Spring Boot: Creating Microservices on Java
Learn all about creating a microservices architecture on Java in this great tutorial.
Join the DZone community and get the full member experience.
Join For FreeWelcome, reader. In this article, we will talk about an interesting architectural model, microservices architecture, in addition to studying one of the new features of Spring 4.0, Spring Boot. But after all, what are microservices?
Microservices
In the development of large systems, it is common to develop various components and libraries that implement various functions, ranging from the implementation of business requirements to technical tasks, such as an XML parser, for example. In these scenarios, several components are reused by different interfaces and / or systems. Imagine, for example, a component that implements a register of customers and we package this component in a java project, which generates his deliverable as a .jar.
In this scenario, we could have several interfaces to use this component, such as web applications, mobile, EJBs, etc. In the traditional form of Java implementation, we would package this jar in several other packages, such as EAR files, WAR, etc. Imagine now that a problem in the customer register is found. In this scenario, we have a considerable operational maintenance work, since as well as the correction on the component, we would have to make the redeploy of all consumer applications due to the component to be packaged inside the other deployment packages.
In order to propose a solution to this issue, was born microservices architecture model. In this architectural model, rather than package the jar files into consumer systems, the components are independently exposed in the form of remote accessible APIs, consumed using protocols such as HTTP, for example.
An important point to note in the above explanations, is that although we are exemplifying the model using the Java world, the same principles can be applied to other technologies such as C #.
Spring Boot
Among the new features in version 4.0 of the Spring Framework, a new project that has arisen is the Spring Boot.O goal of Spring Boot is to provide a way to provide Java applications quickly and simply, through an embedded server - by default it uses an embedded version of tomcat - thus eliminating the need of Java EE containers. With Spring Boot, we can expose components such as REST services independently, exactly as proposed in microservices architecture, so that in any maintenance of the components, we no longer make the redeploy of all the system.
So without further delay, let's begin our hands-on. For this lab, we will use the Eclipse Luna and Maven 3.
To illustrate the concept of microservices, we will create 3 Maven projects in this hands-on: each of them will symbolize back-end functionality, ie reusable APIs, and one of them held a composition, that is, will be a consumer of the other 2.
To begin, let's create 3 simple Maven projects without defined archetype, and let's call them Product-backend, Customer-backend and Order-backend. In the poms of the 3 projects, we will add the dependencies for the creation of our REST services and startup Spring Boot, as we can see below:
.
.
.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
</dependencies>
.
.
.
With the dependencies established, we start coding. The first class that we create, that we call Application, will be identical in all three projects, because it only works as an initiator to Spring Boot - as defined by @SpringBootApplication annotation - rising a Spring context and the embedded server:
package br.com.alexandreesl.handson;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Aplication.class, args);
}
}
The next class we will see is the ApplicationConfig. In this class, which uses the @Configuration Spring annotation to indicate to the framework that it is a resource configuration class, we set the Jersey, which is our ResourceManager responsible for exposing REST services for the consumers.
In a real application, this class would be also creating datasources for access to databases and other resources, but in order to keep it simple enough to be able to focus on the Spring Boot, we will use mocks to represent the data access.
package br.com.alexandreesl.handson;
import javax.inject.Named;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Named
static class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
this.packages(“br.com.alexandreesl.handson.rest”);
}
}
}
The above class will be used identically in the projects relating to customers and products. For the orders, however, since it will be a consumer of other services, we will use this class with a slight difference, as we will also instantiate a RestTemplate. This class, one of the new features in the Spring Framework, is a standardized and very simple interface that facilitates the consumption of REST services. The class to use in the Order-backend project can be seen below:
package br.com.alexandreesl.handson;
import javax.inject.Named;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationConfig {
@Named
static class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
this.packages(“br.com.alexandreesl.handson.rest”);
}
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
}
Finally, we will start the implementation of the REST services themselves. In the project responsible for customer features (Customer-backend), we create a class of DTO and a REST service. The class, which is a simple POJO:
package br.com.alexandreesl.handson.rest;
public class Customer {
private long id;
private String name;
private String email;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setNome(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
The REST service, in turn, has only 2 capabilities, a search of all customers and other that query a customer from his id:
package br.com.alexandreesl.handson.rest;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
@Named
@Path(“/”)
public class CustomerRest {
private static List<Customer> Customers = new ArrayList<Customer>();
static {
Customer customer1 = new Customer();
customer1.setId(1);
customer1.setNome(“Customer 1″);
customer1.setEmail(“customer1@gmail.com”);
Customer customer2 = new Customer();
customer2.setId(2);
customer2.setNome(“Customer 2″);
customer2.setEmail(“Customer2@gmail.com”);
Customer customer3 = new Customer();
customer3.setId(3);
customer3.setNome(“Customer 3″);
customer3.setEmail(“Customer3@gmail.com”);
Customer customer4 = new Customer();
customer4.setId(4);
customer4.setNome(“Customer 4″);
customer4.setEmail(“Customer4@gmail.com”);
Customer customer5 = new Customer();
customer5.setId(5);
customer5.setNome(“Customer 5″);
customer5.setEmail(“Customer5@gmail.com”);
customers.add(customer1);
customers.add(customer2);
customers.add(customer3);
customers.add(customer4);
Customers.add(customer5);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Customer> getCustomers() {
return customers;
}
@GET
@Path(“customer”)
@Produces(MediaType.APPLICATION_JSON)
public Customer getCustomer(@QueryParam(“id”) long id) {
Customer cli = null;
for (Customer c : customers) {
if (c.getId() == id)
cli = c;
}
return cli;
}
}
And that concludes our REST customers service. For products, analogous to customers, we have the methods to search all products or a product through one of his ids and finally we have the orders service, which through a submitOrder method gets the data of a product and a customer - whose keys are passed as parameters to the method - and returns a order header. The classes that make up our services are the following:
package br.com.alexandreesl.handson.rest;
public class Product {
private long id;
private String sku;
private String description;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
package br.com.alexandreesl.handson.rest;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
@Named
@Path(“/”)
public class ProductRest {
private static List<Product> products = new ArrayList<Product>();
static {
Product product1 = new Product();
product1.setId(1);
product1.setSku(“abcd1″);
product1.setDescricao(“Product1″);
Product product2 = new Product();
product2.setId(2);
product2.setSku(“abcd2″);
product2.setDescricao(“Product2″);
Product product3 = new Product();
product3.setId(3);
product3.setSku(“abcd3″);
product3.setDescricao(“Product3″);
Product product4 = new Product();
product4.setId(4);
product4.setSku(“abcd4″);
product4.setDescricao(“Product4″);
products.add(product1);
products.add(product2);
products.add(product3);
products.add(product4);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Product> getProducts() {
return products;
}
@GET
@Path(“product”)
@Produces(MediaType.APPLICATION_JSON)
public Product getProduct(@QueryParam(“id”) long id) {
Product prod = null;
for (Product p : products) {
if (p.getId() == id)
prod = p;
}
return prod;
}
}
Finally, the classes that make up our aforementioned order service in the Order-backend project are:
package br.com.alexandreesl.handson.rest;
import java.util.Date;
public class Order {
private long id;
private long amount;
private Date dateOrder;
private Customer customer;
private Product product;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;}
public Date getDateOrder() {
return dateOrder;
}
public void setDateOrder(Date dateOrder) {
this.dateOrder = dateOrder;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
package br.com.alexandreesl.handson.rest;
import java.util.Date;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.springframework.web.client.RestTemplate;
@Named
@Path(“/”)
public class OrderRest {
private long id = 1;
@Inject
private RestTemplate restTemplate;
@GET
@Path(“order”)
@Produces(MediaType.APPLICATION_JSON)
public Order submitOrder(@QueryParam(“idCustomer”) long idCustomer,
@QueryParam(“idProduct”) long idProduct,
@QueryParam(“amount”) long amount) {
Order order = new Order();
Customer customer = restTemplate.getForObject(
“http://localhost:8081/customer?id={id}”, Customer.class,
idCustomer);
Product product = restTemplate.getForObject(
“http://localhost:8082/product?id={id}”, Product.class,
idProduct);
order.setCustomer(customer);
order.setProduct(product);
order.setId(id);
order.setAmount(amount);
order.setDataOrder(new Date());
id++;
return order;
}
}
The reader should note the use of product and customer classes in our order service. Such classes, however, are not direct references to the ones implemented in other projects, but classes "cloned" of the original, within the order project. This apparent duplication of code in the DTO classes, sure to be a negative aspect of the solution, which can be seen as similar as the stubs classes we see in JAX-WS clients, must be measured carefully as it can be considered a small price to pay, compared to the impact we see if we make the coupling of the projects.
A half solution that can minimize this problem is to create a unique project for the domain classes, which would be imported by all other projects, as the domain classes must undergo a number of much lower maintenance than the services. I leave it to the reader to assess the best option, according to the characteristics of their projects.
Good, but after all this coding, lets get down to, which is to test our services!
To begin, let's start our REST services. For this, we create run configurations in Eclipse where we will add a system property, which specify the port where the spring boot will start the services. In my environment, I started the customer service on port 8081, the products in 8082 and the orders on port 8083, but the reader is free to use the most appropriate ports for his environment. The property to be used to configure the port is:
-Dserver.port=8081
NOTE: If the reader change the ports, it must correct the ports of the calls on the order service code.
With properly configured run configurations, we will start processing and test the calls to our REST. Simply click the icon and select to run each run configuration created, one at a time, which will generate 3 different consoles running in the Eclipse console window. As the reader can see, when we start a project, Spring Boot generates a boot log, where you can see the embedded tomcat and its associated resources, such as Jersey, being initialized:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.2.0.RELEASE)
2014-12-23 21:59:34.567 INFO 8088 --- [ main] br.com.alexandreesl.handson.Application : Starting Application on alexandre-pc with PID 8088 (C:\Users\alexa_000\workspace19122014\Cliente-backend\target\classes started by alexandre in C:\Users\alexa_000\workspace19122014\Cliente-backend)
2014-12-23 21:59:34.613 INFO 8088 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4b1aeb38: startup date [Tue Dec 23 21:59:34 BRST 2014]; root of context hierarchy
2014-12-23 21:59:35.347 INFO 8088 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2014-12-23 21:59:35.590 INFO 8088 --- [ main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2014-12-23 21:59:36.146 INFO 8088 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8081/http
2014-12-23 21:59:36.374 INFO 8088 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2014-12-23 21:59:36.375 INFO 8088 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.0.15
2014-12-23 21:59:36.480 INFO 8088 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2014-12-23 21:59:36.481 INFO 8088 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1871 ms
2014-12-23 21:59:37.258 INFO 8088 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2014-12-23 21:59:37.274 INFO 8088 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2014-12-23 21:59:37.274 INFO 8088 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2014-12-23 21:59:37.274 INFO 8088 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'jerseyServlet' to [/*]
2014-12-23 21:59:37.274 INFO 8088 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2014-12-23 21:59:37.430 INFO 8088 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4b1aeb38: startup date [Tue Dec 23 21:59:34 BRST 2014]; root of context hierarchy
2014-12-23 21:59:37.493 INFO 8088 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-12-23 21:59:37.493 INFO 8088 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-12-23 21:59:37.524 INFO 8088 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-12-23 21:59:37.524 INFO 8088 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-12-23 21:59:37.571 INFO 8088 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-12-23 21:59:37.634 INFO 8088 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2014-12-23 21:59:37.689 INFO 8088 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081/http
2014-12-23 21:59:37.690 INFO 8088 --- [ main] br.com.alexandreesl.handson.Application : Started Application in 3.398 seconds (JVM running for 3.815)
To make just one example, lets call the Order Service. If we call the following URL:
http://localhost:8083/order?idCustomer=2&idProduct=3&amount=4
We produce the following JSON, representing the header of a order:
{"id":1,"amount":4,"dateOrder":1419187358576,"customer":{"id":2,"name":"Customer 2","email":"customer2@gmail.com"},"product":{"id":3,"sku":"abcd3","description":"Product3"}}
At this point, the reader may notice a bug in our order service: subsequent calls will generate the same ID order! This is due to our mock variable that generates the ids be declared as a global variable that is recreated every new instance of the class. As REST services have request scope, every request generates a new instance, which means that the variable is never incremented through the calls. One of the simplest ways of fixing this bug is declaring the variable as static, but before we do that, let's take a moment to think about the fact that we have implemented our projects as microservices - yes, they are microservices! - Can help us in our maintenance:
- If we were in a traditional implementation, each of these components would be a jar file encapsulated within a client application such as a web application (WAR);
- Thus, for fixing this bug, not only we would have to correct the order project, but we would also redeploy the product project, the customer project and the web application itself! The advantages become even more apparent if we consider that the application would have many more features in addition to the problematic feature, so to correct one feature, we would have to perform the redeploy of all others, causing a complete unavailability of our system during reimplantation;
So, having realized the advantages of our construction format, we will initiate the maintenance. During the procedure, we will make the restart of our order service in order to demonstrate how microservices do not affect each other's availability.
To begin our maintenance, we will terminate the Spring Boot of the order process. To do this, we simply select the corresponding console window and terminate. After the stop, if we call the URL of the order service, we have the following error message, indicating the unavailability.
However, if we try to make the product and customer service calls, we see that both are operational, proving the independence.
Then we make the maintenance, changing the variable to the static type:
.
.
.
private static long id = 1;
.
.
.
Finally, we perform a restart of the order service with the implemented correction. If we run several calls to the URL, we see that the service is generating orders with different IDs, proving that the fix was a success:
{"id":9,"amount":4,"dateOrder":1419187358576,"customer":{"id":2,"name":"Customer 2","email":"customer2@gmail.com"},"product":{"id":3,"sku":"abcd3","description":"Product3"}}
We realize, with this simple example, that the independence of such implementations with microservices brought us a powerful architecture: you can undeploy, correct / evolve and deploy new versions of parts of a system, without thereby requiring the redeployment of the whole system and its totally unfeasibility.
Conclusion
And so we conclude our hands-on. With a simple implementation, but powerful, Spring Boot is a good option to implement a microservices architecture and it must be evaluated throughout java architects or developers who wants to promote this model in their demands. Thanks to everyone who supported me in this hands-on, until next time.
Opinions expressed by DZone contributors are their own.
Comments