The Microservices Architecture
The microservices architecture is definitely one of such trendy solutions that is widely considered, especially in new projects. Is it worth trying and what kind of advantages and drawbacks does it have? Let’s see.
Introduction
The recent years and trends have brought us an increasing demand on new solutions regarding an application design. The microservices architecture is definitely one of such trendy solutions that is widely considered, especially in new projects.
Is it worth trying and what kind of advantages and drawbacks does it have? Let’s see.
The monolithic architecture vs. microservices architecture
Assuming we’re building a chat (instant messaging – IM) application that provides us with two sample requirements:
- User Management
- Messaging between individual users and group chats
In the monolithic architecture model, the application will be deployed as a monolith. For instance, in case of choosing the JVM as the target platform, our application will consist of a single WAR/JAR file running on a servlet container e.g. Apache Tomcat.
In the aforementioned picture, once we develop our web application, all the controllers, services and data access objects/repositories as well as other components will reside in the same directory structure. For example, it may be provided by one of the available build tools such as Maven.
In the microservices architecture model, the same application will be deployed as a set of loosely coupled services, where each service provides a set of related functions.
In our example, we will thus have the following services:
- User Service
- Messaging Service
Therefore we will eventually end up with the following details:
- Our services will communicate over HTTP and the REST architecture. However, asynchronous protocols are also available.
- Each service can be developed and deployed independently of one another.
- Each service has its own database to be decoupled from other services.
For ways how to decompose your application into microservices, you may want to take advantage of the two decomposition patterns: Decompose by business capability and Decompose by subdomain.
The following diagram pictures the microservices architecture of our sample application:
As it may be seen on the diagram, from the operating system perspective, microservices:
- Are separate processes, where each micro service has a process identifier (PID) that identifies it in the operating system
- Have unique port numbers so that they represent specific processes and in this way they can communicate with one another.
Service Discovery is a micro service pattern that allows microservices to communicate between each other. It is done thanks to the service registry, where each service registers itself to tell how to identify it by specifying the micro service host, port number and node name.
Once services register themselves them in the registry, they are able to locate one another and communicate.
The microservices architecture in Java
Now it is time to put everything into practice and build a sample application consisting of:
- Service Discovery – To register microservices so that they can communicate
- User Service – Represents the user management feature
- Message Service – Represents the messaging feature
To implement, we will use the Spring Cloud project that provides tools for building patterns in distributed systems and two modules from the Spring Cloud Netflix project that provides integration with the Netflix OSS components.
Technology Stack:
- Spring Boot
- Spring Cloud Netflix Eureka – for Service Discovery
- Spring Cloud Netflix Feign – a declarative REST client
- Maven
1. Implementing the Service Discovery as a micro service
First of all, we need to write a micro service implementing the Service Discovery so that the rest of the microservices will be able to communicate.
We will need the following Maven dependencies:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- Spring Cloud Netflix Eureka for Service Discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Aftter declaring the dependencies, it is necessary to configure the Service Discovery in application.yml
server:
port: 1111
eureka:
client:
register-with-eureka: false
fetch-registry: false
The last thing to do is to implement the ServiceDiscoveryApplication class that will run our Spring Boot micro service application:
@EnableEurekaServer
@SpringBootApplication
public class ServiceDiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceDiscoveryApplication.class, args);
}
}
@EnableEurekaServer
– The annotation that runs the service registry.
Our Service Discovery micro service is ready! Once you run it, it will be assigned a separate PID by the operating system’s kernel and it will run under the port 1111
as specified in application.yml
2. Implementation of the User Service
The pom.xml file declaring dependencies for the User Service micro service can be found in the repository mentioned at the end of the article.
Let’s start with configuration of the application.yml file:
# Specifies the User Service (node) name
spring:
application:
name: user-service
# The port number that the micro service will bind to
server:
port: 2222
# We need to specify the service discovery (Eureka) URL so that the User Service micro service can register there.
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka
The eureka.client.service-url.defaulZone
property specifies the Eureka URL where the User Service will register in order to communicate with the Message Service.
The UserServiceApplication
will run the Spring Boot micro service process:
@EnableEurekaClient
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
@EnableEurekaClient
– Indicates that the micro service application will make into a Eureka instance and a client, so registration and querying the service registry to locate other micro services will be possible.
Once we’re done with the User Service, we can proceed to the Message Service that will call the User Service’s UserController
and the getAccounts()
method by referring to the /users request mapping.
@RestController
public class UserController {
@RequestMapping(value = "/users", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String getAccounts() {
return "Hello from User Service!";
}
}
3. Implementing the Message Service
You can find the pom.xml
file in the repository mentioned at the end of the article.
The application.yml
file configuration is equivalent to that one from the User Service:
# Specifies the Message Service (node) name
spring:
application:
name: message-service
# The port number that the Message Service process will bind to
server:
port: 3333
# We need to specify the service discovery (Eureka) URL so that the Message Service micro service could register there.
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka
Now, let’s define the MessageServiceApplication
class that will run our Message Service micro service process:
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class MessageServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MessageServiceApplication.class, args);
}
}
@EnableFeignClients
scans for interfaces declaring they are Feign clients.
Therefore, once it finds the UserClient
interface, it will automatically instantiate and configure a bean of that type.
Next, let’s define the UserClient interface that will provide communication with the User Service by sending HTTP messages:
@FeignClient("http://user-service")
public interface UserClient {
@RequestMapping(value = "/users", method = RequestMethod.GET)
String getUsers();
}
In @FeignClient
the String value http://user-service
indicates a client name that is used to create a Ribbon load balancer. In this case we have specified a URL.
The Ribbon client will get the physical addresses (host, port number) for the User Service (user-service) which is our second micro service that we would like to call.
The UserClient
bean will be automatically instantiated and configured by Spring during component scan, so it will be able ready for dependency injection.
The Message Service is a Eureka client, so it will resolve the User Service in the service registry (The URL specified in application.yml
– http://localhost:1111/eureka).
The @RequestMapping
annotation used on the getUsers()
method indicates that the HTTP request will be sent with the GET method to the /users
endpoint of the User Service.
The URL will be basically resolved to: http://localhost:2222/users by the Eureka service discovery.
At the end, let’s add the MessageController
that will invoke the UserClient
to communicate with the User Service’s /user
endpoint:
@RestController
public class MessageController {
private final UserClient userClient;
@Autowired
public MessageController(UserClient userClient) {
this.userClient = userClient;
}
@RequestMapping(value = "/messages", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String getMessages() {
return userClient.getUsers();
}
}
In the MessageController
, the UserClient
bean is injected into the userClient
bean with the @Autowired
annotation, since we want to call the User Service.
The getUsers()
method from the UserClient
interface is called in the getMessages()
method.
To run all the micro services, run the service discovery at first so that other micro services could register there:
- Go to the Service Discovery directory
- Run the following command: mvn spring-boot:run
- Then run the User Service and the Message Service micro services with the same command above.
All the three micro services will run as separate processes in an operating system.
We can see whether the User Service and Message Service micro services have registered in the service registry (Eureka) by visiting the service discovery URL: http://localhost:1111/
As we can see in the picture above, our two micro services have registered successfully, so they will be able to communicate via the Service Discovery micro service!
Now, when you open up a web browser and hit the Message Service’s /messages endpoint under http://localhost:/3333/messages, it will call the User Service’s /users endpoint located at http://localhost:2222/users, to get the “Hello from the User Service” message.
The entire source code of the three micro services is available here:
- https://github.com/wjoz/microservices-example-service-discovery
- https://github.com/wjoz/microservices-example-user-service
- https://github.com/wjoz/microservices-example-message-service
References
- http://microservices.io/patterns/index.html
- http://projects.spring.io/spring-cloud/
- https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html
- https://spring.io/blog/2015/01/20/microservice-registration-and-discovery-with-spring-cloud-and-netflix-s-eureka
- https://martinfowler.com/microservices/
- Books: Building Microservices – Designing Fine-Grained Systems by Sam Newman