Introduction
This is a well known scenario: It´s time to add some new functionality to your application and you decide to add a new member to the family of micro-services.
In this example, we are going to use a service called "Sparkker" that will consume stock quotations from anothed service called "Stokker" and will publish the results to another service called "Portfolio-Manager"
This is a diagram showing the whole picture:
It´s obvious that the number of lines connecting the services increase and increase, making the maintenance and configuration of the system very tedious. How can we solve it? We can´t spend the whole day coding integration code, we need to deliver added value!
One possible solution to all this mess is to use some of the components from the Netflix Stack that have been incorporated to the Spring Cloud project and that are very simple to use within Spring Boot. Interested?
Adding an Eureka support to your Spring Boot modules
Eureka! Eureka! (Ancient greek: I found it! I found it!) is a common interjection said by someone eagerly looking for something and the very appropriate name given to a Service Discovery agent withing the Netflix Stack.
What does it do? Very simple. Basically it knows where all the services in an application are, and regularly pings them to see if they are alive or not. Also it can handle many instances of the same service and perform a basic kind of load balancing.
So, whenever a service needs another one, instead of having it configured in its internals settings; it just asks Eureka about it. The result: The configuration settings are reduced to just one: The IP and port of the Eureka Server. Neat!
How can I use it with Spring Boot? Very easy.
Add the Eureka Starter maven dependency (to both server and clients):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-eureka</artifactId> | |
</dependency> |
Then add these two annotations to your server and client modules
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@SpringBootApplication | |
// ... some annotations ommitted for brevity | |
@EnableEurekaServer | |
@EnableEurekaClient | |
public class PortfolioManagerApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(PortfolioManagerApplication.class, args); | |
} | |
// ... ommitted | |
} | |
@SpringBootApplication | |
@EnableEurekaClient | |
// ... ommitted | |
public class SparkkerApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(SparkkerApplication.class, args); | |
} | |
} |
Note: An application can act both as a server and as a client. Why? Because if there are not too many modules and the load of the service discovery is not too high, a regular module can do its own tasks plus the task of the Eureka server.
If we are dealing with many modules, we might want a different JVM to do this work only. You even have the possibility of having several JVM working as servers (knows as peer mode). More info here.
Finally, add the following settings to both your client and server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
spring: | |
application: | |
name: [name_of_service_being_registered] | |
eureka: | |
client: | |
serviceUrl: | |
defaultZone: http://localhost:9999/eureka/ |
And you are good to go! Eureka will handle the sending of hearbeat pings to the clients, registration and de-registration of new clients and the propagation of clients registry to all the services in the system!
Easing up the development with Feign - Declarative REST clients
What would be the traditional approach (in Spring) to make a query to a remote rest service? Something along these lines:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Retrive from the settings the host,port and endpoint of your client | |
String host,port,endpoint; | |
// Build an URI object from the above parameters (and hadle it properly) | |
URI uri = URI.create("http://" + host + ":" + port + "endpoint") | |
// Build a Rest template (bear in mind that you might need to indicate some custom serialization) | |
RestTemplate rt = new RestTemplate(/* Do you need some custom HttpMessageConverter?*/); | |
// Perform the query | |
// Warning: Do you know that is the signature of the method being called? | |
// Does it return a single object? A list of them? | |
// You need to control that as well! | |
StockQuotation resutl = rt.getForObject(uri, StockQuotation.class) |
However, once all of our micro-services are within the Eureka scope, this cumbersome task gets much easier using the Feign Declarative Rest Clients:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
/* By using this annotation, you are indicating that we want to consume an application called "stokker" | |
* Is transparent to us where is the application located and whether it has many instances serving the request | |
*/ | |
@FeignClient("stokker") | |
public interface StokkerClient { | |
/* You just need to indicate the relative URL of the endopoint being called and pass | |
* the same parameters shown in the endpoint description | |
*/ | |
@RequestMapping(method = RequestMethod.GET, value = "/stockQuotationJPAs/search/findValueByStock") | |
List<StockQuotation> getAllStockQuotations(@RequestParam("ticker") String stock); | |
@RequestMapping(method = RequestMethod.GET, value = "/stockQuotationJPAs/search/findTopByStockOrderByTimestampDesc") | |
StockQuotation getLastStockPrice(@RequestParam("ticker") String stock); | |
} |
Easy right? Then, you just need to inject the component in your classes and invoke it as any regular Spring bean. For instance, in Sparkker, we have:
- A controller that receives the customer requests indicating that a new analysis on a stock is required.
- Using the Feign Client, we will get the data from Stokker.
- Finally, we will invoke our Spark-based analysis service to perform the study on the data
- On a next article we will see this analysis in detail!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@RestController | |
public class AnalysisController | |
{ | |
@Autowired | |
private StokkerClient stokkerClient; | |
@Autowired | |
private AnalyzeService analyzeService; | |
@RequestMapping("/analyzeQuote") | |
public Long analyzeQuote(@Param("ticker") String ticker){ | |
// The discovery of the service and request handling is totally transparent to us! | |
List<StockQuotation> quotations = stokkerClient.getAllStockQuotations(ticker); | |
// Once we get the data, we request a new analysis | |
return analyzeService.analyzeStockQuotations(quotations); | |
} | |
} |
Note: In case that the REST service you are consuming is following the HATEOAS approach, you will need to change the signature of your interface´s method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Resources represent the link structure used in HATEOAS | |
*/ | |
@RequestMapping(method = RequestMethod.GET, value = "/stockQuotationJPAs/search/findValueByStock") | |
Resources<StockQuotation> getAllStockQuotations(@RequestParam("ticker") String stock); | |
// In the client side you can access to the payload like this: | |
Resources<StockQuotation> quotations = stokkerClient.getAllStockQuotations(ticker); | |
List<StockQuotation> stockList = new ArrayList<>(quotations.getContent()); | |
// But also you can access the link structure and keep consuming related services! |
And don´t forget to add the feign dependency:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-feign</artifactId> | |
</dependency> |
Finally, annotate your Spring Boot application with this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@SpringBootApplication | |
@EnableEurekaClient | |
@EnableFeignClients | |
public class SparkkerApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(SparkkerApplication.class, args); | |
} | |
} |
Summary
We have quickly dispatched all the boilerplate and now we have our new data analysis module decoupled from everything else (perhaps another machine or even a cluster - if we late use Yarn, for instance -).
Eureka and Feign have greatly eased this task, working without you even noticing it. This is great and, its even greater if we think that these components were born as proprietary components that were later on made open source for all of us to use them!
No comments:
Post a Comment