📜 ⬆️ ⬇️

Reuse of code in microservice architecture - on the example of SPRING BOOT

Hello, habrovchane! Today we offer you another interesting post on the inexhaustible topic of microservices, this time for the luminaries and neophytes of the Java language. We read and vote!

In most microservice architectures there are plenty of opportunities for sharing the code - accordingly, the temptation to do this is also great. In this article I will share my own experience: I will tell you when it is appropriate to reuse the code, and when it is better to avoid it. All points will be illustrated with a sample project using Spring Boot, which is available on Github .

INTRODUCTION

Before we talk about sharing the code and what is behind it, we will determine which tasks are usually solved using microservice architectures. Here are the main benefits of introducing microservices:
')

Naturally, many such advantages allow not only to build a better system, but also make life easier for the developer and make his work more grateful. Of course, you can argue about them as much as you like, so let's just agree on the fact that microservices are useful (which is confirmed by the experience of large companies like Netflix and Nginx). As with any other architecture, microservices are characterized by their own shortcomings and difficulties that need to be overcome. The most important are:


PROBLEM

So, here we come to the question faced by most of the teams that start working with microservices. Considering what the goal of working with microservices and recommended methods for their implementation, we face the problem: “We need weakly connected services, between which there will be almost no common code and dependencies. Thus, whenever we consume some service, we need to write classes that will handle the response. But what about the principle of "DRY" (Do not repeat)? What to do?". In this case, it is easy to hit two anti-patterns:


DECISION

If you clearly articulate the purpose of the architecture and how the problem should be explained, the solution seems to suggest itself. If the service code should be completely autonomous, but we need to consume rather complex responses on clients, then clients should write their own libraries to consume this service.

This approach has the following advantages:


This solution cannot be called completely new - this is exactly the approach described in the book “ Creating Microservices ” by Sam Newman (I highly recommend). The embodiment of these ideas is found in many successful microservice architectures. This article is mainly devoted to the reuse of code in the domain, but similar principles apply to code that provides general connectivity and information sharing, since this does not contradict the principles outlined here.

Another question is possible: is it worth to worry about the binding of domain objects and connectivity with client libraries. As with the answer to our main question, the most important factor in this case is the influence of such details on the overall architecture. If we decide that the performance will increase, if we include the linking code in the client libraries, then we need to ensure that there will not be a strong link between the client services. Considering that connectivity in such architectures is usually provided with simple REST calls, or with the help of a message queue, I do not recommend putting such code in the client library, since it adds unnecessary dependencies but is not very profitable. If the code for connectivity has something special or too complex — for example, client certificates for performing SOAP requests, it may be advisable to attach an additional library. If you choose this path, always set the use of the client library as optional, not mandatory. Client services do not have to fully own the code (it is impossible to oblige the service provider to update the corresponding client libraries without fail).

EXAMPLE WITH SPRING BOOT



So, I explained the solution, and now I will demonstrate it in code. By the way, here is the opportunity to once again promote my favorite microservice library - Spring Boot . The whole example can be downloaded from the repository on Github , created specifically for this article.

Spring Boot allows you to develop microservices right off the bat - yes, I'm not exaggerating. If Dropwizard seemed fast, then you are quite surprised at how comfortable it is to work with Spring Boot. In this example, we are developing a very simple User service that will return a simulated User JSON object. In the future, this service will be used by the notification service and the table service, in fact, building a variety of data views; however, in both cases, the service needs to understand the User object.

USER SERVICE

In UserServiceApplication will be the main method. Since this is Spring Boot, it also includes the built-in Tomcat server at startup:

 package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } } 

Indeed, it cannot be simpler! Spring Boot is a very categorical framework, so if defaults suit us, then almost nothing is necessary to manually type. However, you still have to change one thing: we are talking about the default port number. Let's see how this is done in the application.properties file:

 server.port = 9001 

Simple and beautiful. If you have ever written a REST service in Java, then you probably know that you need a Controller for this. If you are doing this for the first time - do not worry, writing controllers in Spring Boot is quite simple:

 package com.example; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @RequestMapping("/user") public User getUser(@RequestParam(value="id", defaultValue="1") int id) { return new User(id); } } 

So we simply allow the user to perform requests to the endpoint /user?id= , where the id can correspond to any user that interests us. Considering how simple these classes are - in fact, all the logic must lie in a particular User class. This class will generate procurement data and will be serialized using Jackson (JSON library for Java):

 package com.example; import java.util.ArrayList; import java.util.List; public class User { private final long id; private final String forename; private final String surname; private final String organisation; private final List<String> notifications; private final long points; //        private final List<String> friends; public User(int id) { String[] forenames = {"Alice", "Manjula", "Bartosz", "Mack"}; String[] surnames = {"Smith", "Salvatore", "Jedrzejewski", "Scott"}; String[] organisations = {"ScottLogic", "UNICEF"}; forename = forenames[id%3]; surname = surnames[id%4]; organisation = organisations[id%2]; notifications= new ArrayList<>(); notifications.add("You have been promoted!"); notifications.add("Sorry, disregard the previous notifaction- wrong user"); points = id * 31 % 1000; //     friends = new ArrayList<>(); this.id = id; } //      … } 

This is all the service needed to create User JSON. Since this is the first Spring Boot service we are considering, it will not hurt to look into the .pom file:

 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>user-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>user-service</name> <description>Demo user-service with Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <relativePath/> <!--      --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <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> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 

When calling the service, whose id is 10, we see the following JSON output:



CLIENT LIBRARY

Suppose we have two services that use this API - a notification service and a personal account. In a realistic example, a User object could be much more complicated, and we could have more than two clients. The client library, a simple project called user-client-libs , consists of a single class:

 @JsonIgnoreProperties(ignoreUnknown = true) public class UserView { private long id; private String forename; private String surname; private String organisation; private List<String> notifications; private long points; public UserView(){ } public long getId() { return id; } public String getForename() { return forename; } public String getSurname() { return surname; } public String getOrganisation() { return organisation; } public List<String> getNotifications() { return notifications; } public long getPoints() { return points; } } 

As you can see, this class is simpler - there are no details associated with imitating users; there is no friends list, which is considered undesirable in the original class. We hide these details from customers. In such a lightweight implementation, the new fields that this API can return will also be ignored. Of course, in a realistic example, the client library could be much more complicated, which would save us time spent on typing the stencil code and help us better understand the relationships between the fields.

CUSTOMERS

This example shows the implementation of two separate client services. One is needed to create a "user account", and the other is for a "list of notifications." You can consider them as specialized microservices for working with user interface components.

Here is the personal account service controller:

 import com.example.user.dto.UserView; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserDashboardController { @RequestMapping("/dashboard") public String getUser(@RequestParam(value="id", defaultValue="1") int id) { RestTemplate restTemplate = new RestTemplate(); UserView user = restTemplate.getForObject("http://localhost:9001/user?id="+id, UserView.class); return "USER DASHBOARD <br>" + "Welcome " + user.getForename() +" "+user.getSurname()+"<br>"+ "You have " +user.getPoints() + " points! Good job!<br>"+ "<br>"+ "<br>"+user.getOrganisation(); } } 

And this is a personal notification service controller:

 import com.example.user.dto.UserView; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserNotificationController { @RequestMapping("/notification") public String getUser(@RequestParam(value="id", defaultValue="1") int id) { RestTemplate restTemplate = new RestTemplate(); UserView user = restTemplate.getForObject("http://localhost:9001/user?id="+id, UserView.class); String response = "NOTIFICATIONS"; int number = 1; for(String notification : user.getNotifications()){ response += " Notification number "+(number++)+": "+notification; } return response; } } 

As you can see, both clients are very simple, and the connection between them and the service is also trivial. Of course, we must add dependencies for both services to the .pom files.

 <dependency> <groupId>com.example</groupId> <artifactId>user-client-libs</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> 

All that remains to be done in this example is to run all three services on ports 9001, 9002 and 9003 to see the output:

Personal Area:



Notifications:



CONCLUSION

I believe that this approach to design allows us to solve most of the problems with the reuse of code in the microservice architecture. It is understandable, avoids most of the shortcomings inherent in other approaches and simplifies the life of the developer. Moreover, it is a solution tested on real projects and well-proven.

In the example with Spring Boot, it is clearly demonstrated how convenient this approach is; besides, it turns out that microservices are much simpler than they might seem. If you want to study this project in more detail - look at my Github and try to develop it.

Good luck with the development of microservices!

PS - from the authors of the translation:

→ Here is a book about Spring Boot.
→ Here is a book about microservices in Spring

Want some?

Source: https://habr.com/ru/post/322102/


All Articles