I have implemented a Spring Cloud based application that has the following principal architecture. The GatewayService delegates to multiple instances of HelloService. I use Consul for service discovery. GatewayService is implemented in the following way:
#EnableDiscoveryClient
#SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
#Bean
#LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
#RestController
static class GateWayController {
private final WebClient.Builder webClientBuilder;
#Data
#NoArgsConstructor
private static class Response {
private String message;
private String appName;
private int port;
}
#Autowired
public GateWayController(WebClient.Builder webClientBuilder) {
this.webClientBuilder = webClientBuilder;
}
#GetMapping("/lbhello")
public Mono<Response> hello() {
return webClientBuilder.build()
.get()
.uri("lb://hello-service/hello")
.retrieve()
.bodyToMono(Response.class);
}
}
}
spring-cloud-starter-consul-discovery and spring-cloud-starter-loadbalancer areincluded as a dependencies in pom.xml. In application.yaml I only set application.name and disable Ribbon, so that Spring Cloud Loadbalancer is used. When I start the GatewayService and two instances of HelloService everything works as expected. Service calls to the lbhello endpoint are delegated alternately to the two HelloService instances.
When one instance of HelloService is stopped, Consul detects that the instance is missing an marks it as critical. As a consequence I would expect that the load balancer stops forwarding requests to the non-existing service, at least after some time. But this never happens. Every second service call is delegated to this service instance and consequently fails. When using Eureka as a discovery service the registry is adapted and only the remaining HelloService is considered for service calls.
Is this the expected behavior for Consul? If the answer is yes, is there a Spring Cloud setting to adapt this behavior?
The Spring Cloud ConsulDiscoveryClient loads all service instance by default, regardless of their health state. This behavior can be changed by the following configuration setting:
spring.cloud.consul.discovery.query-passing = true
With this setting request are not delegated to non-healty instances after some delay.
Related
I am currently using Spring Cloud Function, and I want to deploy my functions on AWS Lambda, using the adapter for AWS.
Till now all the Spring Cloud Function implemented was a single function with the following structure.
#SpringBootApplication
#ComponentScan(basePackages = "com.demo")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Handler for lamda:
import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler;
public class DemoHandler extends SpringBootRequestHandler<DemoRequest, DemoResponse> {
}
I understand that this part of the code is Depricated.
Interface to define the Endpoint:
#SuppressWarnings("hiding")
#FunctionalInterface
public interface Demoapi<DemoRequest, DemoResponse>{
#PostMapping(value = "/v1/demo")
public ResponseEntity<DemoResponse> demo(#RequestBody DemoRequest demoInfo);
}
Followed by the controller where the #RestController was defined
Part of the controller:
#RestController
public class Democontroller implements Demoapi<DemoRequest, DemoResponse>{
static final Logger LOGGER = LogManager.getLogger(Democontroller.class);
#Autowired
private DemoService demoService;
#Override
public ResponseEntity<DemoResponse> demo(#RequestBody DemoRequest demoInfo) {
DemoResponse demoResponse=new DemoResponse();
try {
demoResponse = demoService.demofun(demoInfo);
........
.........
.........
This setup works perfectly. When deploying to Lambda, I provide Handler as com.demo.DemoHandler in the AWS console, and in the Environment Variable under FUNCTION_NAME I give the DemoController class with the Starting letter in small caps i.e demoController and the lamda works fine when tested in the console as well as directed from an API Gateway.
Currently I am working on a project where it is required to perform all the CRUD operations within one Spring Boot Application and deploy it as a single lambda.
I want to know how this can be achieved using similar structure within the application.
All help is highly appreciated!
You can achieve a similar result using an approach with this library:
https://github.com/MelonProjectCom/lambda-http-router
#PathPostMapping("/example/test")
public String example(#Body String body) {
return "Hello World! - body: " + body;
}
I'm trying to apply Prometheus metrics using the micrometer #Timed annotations.
I found out that they only work on controller endpoints and not "simple" public and private methods.
Given this example:
#RestController
public class TestController {
#GetMapping("/test")
#Timed("test-endpoint") //does create prometheus metrics
public String test() {
privateMethod();
publicMethod();
return "test";
}
#Timed("test-private") //does NOT create prometheus metrics
private void privateMethod() {System.out.println("private stuff");}
#Timed("test-public") //does NOT create prometheus metrics
public void publicMethod() {System.out.println("public stuff");}
}
creates the following metrics:
...
# HELP test_endpoint_seconds
# TYPE test_endpoint_seconds summary
test_endpoint_seconds_count{class="com.example.micrometerannotationexample.TestController",exception="none",method="test",} 1.0
test_endpoint_seconds_sum{class="com.example.micrometerannotationexample.TestController",exception="none",method="test",} 0.0076286
# HELP test_endpoint_seconds_max
# TYPE test_endpoint_seconds_max gauge
test_endpoint_seconds_max{class="com.example.micrometerannotationexample.TestController",exception="none",method="test",} 0.0076286
...
No metrics found for #Timed("test-private") and #Timed("test-public"), why is that?
Note: I've read on this github thread, that Spring Boot does not recognize #Timed annotations on arbitrary methods and that you need to manually configure a TimedAspect Bean in order for it to work. I've tried that but still it yields no results.
#Configuration
#EnableAspectJAutoProxy
public class MetricsConfig {
#Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
To try this locally see necessary gist here
#Timed works only on public methods called by another class.
Spring Boot annotations like #Timed / #Transactional need the so-called proxying which happens only between invocations of public methods.
A good explanation is this one https://stackoverflow.com/a/3429757/2468241
I'm trying to test if my spring boot application correctly receives messages from an AWS SQS queue by using spring clouds aws messaging.
Listener:
#Component
public class RequestListener {
private static final Logger LOG = LoggerFactory.getLogger(RequestListener.class);
private final MyService myService;
public RequestListener(MyService myService) {
this.myService = myService;
}
#SqsListener(value = "${aws.queue.url}", deletionPolicy = SqsMessageDeletionPolicy.ALWAYS)
void getRequest(MyRequest request) {
LOG.info("Fetched new message from SQS: {}", request);
myService.processRequest(request);
}
}
Test:
#ExtendWith(SpringExtension.class)
#ActiveProfiles(SpringProfiles.TEST)
#SpringBootTest(webEnvironment = RANDOM_PORT)
class TestClass {
...
#MockBean
MyService myService;
#Test
void testReceiveMessage() {
var payload = MyRequest.builder().content("foo").build();
messagingTemplate.convertAndSend(testQueueUrl, payload);
when(myService.processRequest(payload)).thenReturn(true);
verify(myService, Mockito.timeout(2000).atLeastOnce()).processRequest(payload);
}
...
The message is received everytime (I see the log) but there's a 50% chance that the verify will fail as the Mock is never called (Error: zero interactions). I first thought this might be some synchronization issue (thus the timeout) but I can actually see that the real MyService service is called and not the Mock whenever the test fails.
I run the tests using mvn clean install
So my question is, how is it possible that randomly somtimes the Mock and sometimes the real object is called?
I also tried deactivating the real service with a custom #Profile annotation for the test but that didn't change the behaviour either.
Tested with Boot-Version 2.3.9.RELEASE and spring cloud HOXTON.SR10
(and also with Boot-Version 2.4.1 and cloud version HOXTON.SR10)
Thanks for any hints
There is a microservice made with Spring Boot. It consist of Jetty, Jersey, Jackson and Liquibase. One of tasks of this service is to receive some data via REST and return response. This operation goes through next units:
MyResource, #Component REST with JAX-RS annotations, that receives data and ask #Autowired MyService for response.
MyService, #Component service with #Autowired MyResource to ask it for response.
MyResource, simple #JpaRepository interface
This application works fine, but now I need to add some tests for every module. Usually I use Mockito to test units, so I test my service with mocked MyRepository (Mockito #Mock annotation + when() method). I want to test MyResource.java the same way.
I tried to use TestRestTemplate way to test with spring-boot-starter-test and my test class looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebIntegrationTest(randomPort = true)
public class MyResourceTest {
#Value("${local.server.port}")
private int port;
private String getBaseUrl() {
return "http://localhost:" + port;
}
#Test
public void test() {
final TestRestTemplate restTemplate = new TestRestTemplate();
assertEquals(restTemplate.postForEntity(getBaseUrl() + "/test", null, ResponseObject.class).getBody(), new ResponseObject());
}
}
And there are two problems. First - when my test is running, they run up whole spring application, so my liquibase scripts is trying to find database and this is a very long-time process. Second - I can't replace MyService class with Mockito proxy.
I tried to find some manuals about best practices in testing spring boot REST applications and I found MockMvc-based way, but it looks like don't run up server to run test. Can you please share your way to test REST resource in spring boot?
MockMvc is the prefered solution for your problem.
It runs the spring boot application but 'mocks' the request,so it does not really run over http but behaves as such.
You can get all beans of your application injected into your test class using #Autowired, so you can mock spring beans there.
I run all my tests over 6 classes of configuration/support.
AbstractTest to configure core of tests
#ActiveProfiles(resolver = TestActiveProfilesResolver.class)
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest
#SpringApplicationConfiguration(classes = Application.class)
public abstract class AbstractTest {
...
}
AbstractRepositoryTest to test my repositories over jdbc+jdbi
#Transactional
public abstract class AbstractRepositoryTest<R> extends AbstractTest implements Repositories {
#Inject
private ObjectMapper mapper;
#Inject
protected Repositories repositories;
private final Class<R> repositoryType;
...
}
AbstractServiceTest to test my services, this class contains the core of my service's test
public abstract class AbstractServiceTest {
...
}
AbstractIntegrationTest contains the core of my integration tests, utils methods to test controller's, etc.
public abstract class AbstractIntegrationTest extends AbstractTest {
...
}
Application provides to AbstractTest a context to run tests, this classe is same class that I use to run my spring boot application
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(Application.class);
}
...
}
And finnaly the TestActiveProfilesResolver, that provide the profile to match application-test.properties, It's necessary because exist the open issue on JIRA, here
public class TestActiveProfilesResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(final Class<?> testClass) {
final String activeProfile = System.getProperty("spring.profiles.active");
return new String[] {activeProfile == null ? "test" : activeProfile};
}
}
Sometimes my tests extend AbstractRepositoryTest, AbstractIntegrationTests or AbstractServiceTest, it depends on what I want.
This configuration solved all my problems to test services, controllers etc.
I am looking for the way to add embedded elasticsearch to my spring boot integration test.
I looked at elastic search integration test but it does not work together with spring boot as both should uses different test runner.
I have a class test as below unfortunately it does not work with error:
java.lang.IllegalStateException: No context information for thread:
Thread[id=1, name=main, state=RUNNABLE, group=main]. Is this thread
running under a class
com.carrotsearch.randomizedtesting.RandomizedRunner runner context?
Add #RunWith(class
com.carrotsearch.randomizedtesting.RandomizedRunner.class) to your
test class. Make sure your code accesses random contexts within
#BeforeClass and #AfterClass boundary (for example, static test class
initializers are not permitted to access random contexts).
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = App.class)
#WebAppConfiguration
#IntegrationTest("server.port:0")
public class TestExample extends ElasticsearchIntegrationTest {
TestRestTemplate testRestTemplate = new TestRestTemplate();
#Value("${local.server.port}")
int port;
#Test
public void testOne(){
ResponseEntity<String> results = testRestTemplate.getForEntity(String.format("http://localhost:%d/client/1", port), String.class);
System.out.print(results);
}
}
Does anybody has some ideas how to make them run or what is alternatives ??
You can actually do what you need without any additional elasticsearch testing dependencies. The idea is basically to create an embedded node and then use the NodeClient to communicate with it.
For that, I created my own EmbeddedElasticsearchServer class which looks (more or less) like this:
public class EmbeddedElasticsearchServer implements InitializingBean {
public EmbeddedElasticsearchServer() {
ImmutableSettings.Builder elasticsearchSettings = ImmutableSettings.settingsBuilder()
.put("http.enabled", "false")
.put("path.data", "target/elasticsearch-data");
node = nodeBuilder()
.local(true)
.settings(elasticsearchSettings.build())
.node();
client = node.client();
}
#Override
public void afterPropertiesSet() throws Exception {
// Initialization stuff:
// - create required indices
// - define mappings
// - populate with test data
}
public Client getClient() {
return client;
}
}
Then, in spring configuration (let's call it integration-test-context.xml) I did this:
<bean id="embeddedElasticsearchServer"
class="com.example.EmbeddedElasticsearchServer" />
<bean id="elasticsearchClient"
class="org.elasticsearch.client.node.NodeClient"
factory-bean="embeddedElasticsearchServer"
factory-method="getClient" />
Then you can just autowire the client in your test like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/integration-test-context.xml")
public abstract class AbstractElasticsearchIntegrationTest {
#Autowired
private Client elasticsearchClient;
// Your rests go here...
}