[JAVA] I tried to make the sample application into a microservice according to the idea of the book "Microservice Architecture".

Introduction

I am learning about microservices based on O'Reilly's Microservice Architecture (hereinafter referred to as "books"), but I actually learned it myself. If you don't output it, you won't get an image.

  1. Prepare a simple monolith application.

  2. In line with the idea introduced in the book, convert the monolith application prepared in 1 into a microservice.

I tried to experience the flow.

However, deployment and security are out of the scope of this article (because we have prepared only a sample application and we do not intend to deploy it, and we do not need to consider security in the local environment for the operation check environment).

Sample app

The code can be found at Bitbucket.

The environment where the operation was confirmed is as follows.

  $ java --version
  openjdk 12.0.1 2019-04-16
  OpenJDK Runtime Environment (build 12.0.1+12)
  OpenJDK 64-Bit Server VM (build 12.0.1+12, mixed mode, sharing)
  $ ./gradlew --version

  ---

  Gradle 5.6.2

  Build time: 2019-09-05 16:13:54 UTC
  Revision: 55a5e53d855db8fc7b0e494412fc624051a8e781

  Kotlin: 1.3.41
  Groovy: 2.5.4
  Ant: Apache Ant(TM) version 1.9.14 compiled on March 12 2019
  JVM: 12.0.1 (Oracle Corporation 12.0.1+12)
  OS: Mac OS X 10.14.6 x86_64

Overview of sample app

Domain model

The sample app uses the layered architecture introduced in Eric Evans's Domain Driven Design. Here, only the domain layer in it is shown as a model. サンプルアプリのドメインモデル

The first excuse is that this model has the following uncool parts (I knew it wasn't cool, but I was bothered and cut corners).

--The Task class is not immutable.

――We are basically supposed to make it immutable.

--Business knowledge is not reflected in the method name of the repository.

--Like the Task class, TaskRepository # changeTaskStatus should be something like TaskRepository # undone.

--The Task class is shared by management and report.

--report does not require TaskId etc., so it should be modeled as a different type.

Task management context

--The name of the context to which the management package of the domain model belongs.

--Provides the ability to register, update and delete tasks.

Report context

--The name of the context to which the report package of the domain model belongs.

--Calculate the number of remaining tasks (RemainingTasks) and the total number of tasks (TotalTasks) among the registered tasks.

--The number of remaining tasks is the total number of tasks whose task status is ʻUNDONEorDOING`. --The total number of tasks is the total number of registered tasks (regardless of task status).

How to run the sample app

I use curl to check the operation of the application, so if you haven't installed it yet, install it (I skipped it because it was troublesome to prepare the UI).

Launch the app

Move to the cloned directory and execute the following command.

$ ./gradlew bootRun

Task registration

$ curl -D - -H "Content-type: application/json" -X POST -d '{"title":"Task title", "summary":"Task summary"}' http://localhost:8080/task/management/register

'{" title ":" task title "," summary ":" task summary "} You can set any value for the value part of. However, title must be 20 characters or less and summary must be 50 characters or less.

Confirmation of registered tasks

$ curl http://localhost:8080/task/management/tasks

Change task title

$ curl -D - -H "Content-type: application/json" -X PUT -d '{"title":"New task title"}' http://localhost:8080/task/management/changeTaskTitle/<taskId>

For <taskId>, specify the ID of the task whose title you want to change. Check the task ID with the command introduced in [Confirm Registered Task](# Confirm Registered Task).

Change task summary

$ curl -D - -H "Content-type: application/json" -X PUT -d '{"summary":"New task summary"}' http://localhost:8080/task/management/changeTaskSummary/<taskId>

For <taskId>, specify the ID of the task whose title you want to change. Check the task ID with the command introduced in [Confirm Registered Task](# Confirm Registered Task).

Change task status

$ curl -D - -H "Content-type: application/json" -X PUT -d '{"taskStatus":1}' http://localhost:8080/task/management/changeTaskStatus/<taskId>

For the value of '{" taskStatus ": 1}', specify one of 0, 1, and 2. By the way, 0 is not implemented, 1 is in progress, and 2 is already implemented.

Also, in the <taskId> part, specify the ID of the task whose title you want to change. Check the task ID with the command introduced in [Confirm Registered Task](# Confirm Registered Task).

Check progress

$ curl http://localhost:8080/task/report/progress

Display the number of remaining tasks and the number of all tasks.

What is microservices?

Microservices are explained in the book as follows.

Microservices is the first software architecture proposed by ThoughtWorks' Martin Fowler and James Lewis. By dividing the monolithic architecture into multiple small "microservices" according to business functions and making them work together, we are trying to realize the advantages of rapid deployment, excellent resiliency and scalability. It is something to do.

When modifying a monolithic system, it is necessary to stop, rebuild, and redeploy the server, so it is necessary for system personnel to check the impact on the system and adjust the stop timing. When the microservice architecture is adopted, each microservice is independent, so there is an advantage that such adjustment work becomes unnecessary.

Personally, I think that this ** ease of deployment ** is the greatest merit of microservices, but the book also mentions the following about the merit of microservices.

merit Description
Technological singularity You can choose your favorite technology for each microservice.
Restorability If one microservice fails, other microservices can continue to function.
scaling Only microservices that need to be scaled can be scaled.
Organizational match Placing people who develop a microservice in the same organization eliminates interaction between organizations and facilitates development (Conway's law )。Conway's lawについては後述する。
Compositability In microservices, various functions can be used in a way that suits the purpose.
Of course, some monolithic systems have a mouth that provides functionality, but their particle size is usually coarse and often inconvenient.
Optimization to make it replaceable Microservices (compared to monolithic systems) are smaller, making refactoring and replacement costs easier to manage.

Did you understand the benefits of microservices? After that, the sample application prepared in advance will be converted into a microservice step by step.

Determine the unit of microservices

One of the challenges in microservices will be to determine the appropriate service boundaries. If the scale of microservices is too small, the ties between services will be strong and the benefits of microservices cannot be enjoyed, and if the scale is too large, the same benefits cannot be enjoyed.

The book cites two concepts in defining the boundaries of microservices: ** loosely coupled ** and ** highly aggregated **. This means that the boundaries should be defined so that the associated behavior exists in one microservice and communication with other boundaries is as low as possible.

As a tool for finding such boundaries, there is ** Bounded Context ** introduced in "Eric Evans's Domain Driven Design". --Glossary of Domain Driven Design](see https://qiita.com/little_hand_s/items/2929b6323bf1bc6d0d0d).

However, finding a bounded context requires deep business knowledge, so adopting a microservices architecture for new system development is more likely to fail. In fact, the book also gives a failure case of ThoughtWorks, and recommends a method of building a monolithic system at first and then converting it to a microservice when the understanding of the business is deepened.

(In response to the episode that a tool newly developed by ThoughtWorks was developed with microservices, but it was merged into one monolithic system on the way, and after deepening the understanding of the business, it was divided into microservices again). This is not the only example of this situation. Decomposing a system into microservices can be costly, especially for first-time domains. In many ways, having an existing code base that you want to break down into microservices is much easier than working on microservices from scratch.

Since this sample app was designed and implemented in advance according to the idea of Domain Driven Design, it is already divided into two bounded contexts. ** Task management context ** and ** Report context **. This time we'll make these two contexts microservices.

Even if you say "make it a microservice", no particularly difficult work will occur, only the repository configuration will be changed as follows.

モノリスとマイクロサービスにおけるリポジトリ構成

Conway's law

In this article, we have bounded services according to a bounded context, but there are other ways to follow ** Conway's Law **. What is Conway's Law? The following description stated in Melvin Conway's paper published in April 1968. Is.

Every organization that designs a system (here, a system that is broadly defined rather than just an information system) always produces a design that follows the communication structure of that organization.

In a nutshell, ** organizational structure and software architecture are closely related, so service boundaries should be defined according to organizational structure **. By the way, the reverse (changing the organizational structure according to the architecture) is called the reverse Conway's law.

Microservice repository

--Task Management Service Repository

--Report Service Repository

Decide how to integrate microservices

Next, let's consider the integration (how to cooperate) between the divided microservices. Currently, we are referencing one database in two contexts.

現状のサービスとデータベースの関係

The pattern in which multiple microservices refer to one database in this way is called ** shared database ** in books, and is called an anti-pattern. This is because changing the data structure for the convenience of the task management service affects the reporting service, which should not be relevant (contrary to the microservice design principles ** highly cohesive ** and ** loosely coupled **).

Therefore, the database to be referenced is separated as follows.

マイクロサービス化後のマイクロサービスとデータベースの関係案(1)

With this configuration, each microservice can change the data structure without worrying about other microservices (that is, the independence of the microservice increases).

Congratulations on this ... but how to synchronize the new task management service and reporting service databases? The problem arises.

Use a data pump

One of the countermeasures to this problem is to periodically send the data held in the database of the task management service to the report service. This mechanism is called ** data pump **. The data pump itself is a simple program started by Cron.

データポンプ

However, this method has the following problems.

  1. Data Pump developers are required to have a deep knowledge of the databases owned by the task management and reporting services. In other words, the independence of microservices is impaired.

  2. Data cannot be synchronized in real time.

If the above problem cannot be tolerated, a method called ** event data pump ** is effective.

イベントデータポンプ

The task management service publishes events related to state changes such as task registration events and title change events, and the reporting service subscribes to those events. Event data pumps do not need to know the internal details of other services, so the coupling between microservices can be weakened (compared to data pumps).

Use the public API of the task management service

There is also a proposal that the report service does not own the database and inquires the task management service each time via the API published by the task management service.

公開APIを利用する方法

If the amount of data returned as a result of an inquiry (total number of tasks) is small and communication costs are low, or if the frequency of inquiries is low, this method may be sufficient without creating a data pump.

This time, there is no problem in acquiring data each time via the public API, so we will make inquiries via REST communication.

Prepare for obstacles

If you want to check the progress, the report service inquires the task management service over the network. That is, if a network failure has occurred, this query will fail. Not limited to network failures, the task management service also fails when it is under maintenance and the service is stopped.

In other words, when adopting a microservices architecture, it is necessary to design in advance considering the possibility of inter-service communication failure.

Introduce a circuit breaker

The circuit breaker is a mechanism that "stops the call of the downstream service after a certain number of failures (checks whether the downstream service has been restored and automatically restarts it when it is restored)".

For example, in this case, when the user inquires about the progress of the report service, the report service acquires the task list from the task management service. Suppose that the API call to get this task list fails for some reason (see the figure below).

サーキットブレーカー(1)

If the API call fails a certain number of times, the report service stops communicating with the task management service and returns a predetermined error processing execution result to the user (see the figure below). This state is called "circuit breaker trips".

In the sample, when the circuit breaker goes down, the log is output and the default progress value (progress value where both the number of remaining tasks and the total number of tasks are 0) is returned.

サーキットブレーカー(2)

When the circuit breaker trips, a health check is performed from time to time, and when communication with the task management service is restored, the value obtained from the task management service is returned to the user (see the figure below).

サーキットブレーカー(3)

This concludes the explanation of the circuit breaker. The circuit breaker function does not need to be implemented from scratch. For example, when developing in Java, it can be easily implemented by using a library called Hystrix.

TaskReportService.java


@Service
@AllArgsConstructor
public class TaskReportService {
  @Autowired private final RestComponent restComponent;

  private static final Logger logger = LogManager.getLogger(TaskReportService.class);

  @HystrixCommand(
      fallbackMethod = "executeFallback",
      commandProperties = {
        @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5")
      })
  public Progress getProgress() {
    //Process to get task list from task management service and return progress to Controller (omitted)
  }

  //Error handling when communication with task management service is not possible
  private Progress executeFallback(Throwable throwable) {
    logger.error(throwable.getMessage());
    return new Progress(Collections.emptyList());
  }
}

The contents specified by @ HystrixCommand are as follows.

--Enable communication timeout with task management service (Set ʻexecution.timeout.enabled to true`).

--Set the communication timeout time to 5000 milliseconds (Set ʻexecution.isolation.thread.timeoutInMilliseconds to 5000`).

--Set the threshold to 5 times (set circuitBreaker.requestVolumeThreshold to 5).

--Specify the error handling method to be executed when the communication timeout with the task management service occurs 5 times (set fallbackMethod to ʻexecuteFallback`).

Build a log environment

This time, two microservices are run on one host, but in the case of microservices, only one microservice is run on one host.

If you want to check the log for service status check or error analysis, it is difficult to check by SSH to each host (because it is necessary to collect the log of each service and arrange it in chronological order). Therefore, use Logstash to send the logs output by each microservice to the downstream system (this time using Elasticsearch). Also, use Kibana to view the logs sent to Elasticsearch in real time. This configuration is called ELK, which is an acronym for each system.

ELK environment construction

The storage location of the yaml file may differ depending on your environment, so read it as appropriate.

Install Elasticsearch

First, install Elasticsearch with the following command.

$ brew install elasticsearch

After the installation is complete, edit /usr/local/etc/elasticsearch/elasticsearch.yml as follows.

elasticsearch.yml


#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
network.host: localhost

Elasticsearch is executed by the following command.

$ elasticsearch

Install Logstash

Then install Logstash with the following command.

$ brew install logstash

After the installation is complete, edit /usr/local/etc/logstash/logstash-sample.conf as follows (path / to / log / file of file is the log output destination of the report service. specify).

logstash-sample.conf


# Sample Logstash configuration for creating a simple
# Beats -> Logstash -> Elasticsearch pipeline.

input {
  file{
    path => "path/to/log/file"
    start_position => "beginning"
    sincedb_path => "/dev/null"
  }
}

output {
  elasticsearch {
    hosts => ["http://localhost:9200"]
  }
}

Logstash is executed by the following command.

$ sudo logstash -f /usr/local/etc/logstash/logstash-sample.conf

Install Kibana

Finally, install Kibana with the following command.

$ brew install kibana

After the installation is complete, edit /usr/local/etc/kibana/kibana.yml as follows.

kibana.yml


# Specifies the address to which the Kibana server will bind. IP addresses and host names are both valid values.
# The default is 'localhost', which usually means remote machines will not be able to connect.
# To allow connections from remote users, set this parameter to a non-loopback address.
server.host: "localhost"

(Omitted)

# The URLs of the Elasticsearch instances to use for all your queries.
elasticsearch.hosts: ["http://localhost:9200"]

Kibana is executed by the following command.

$ kibana

This completes the ELK environment construction.

Checking the operation of microservices

This completes the work of converting the sample application into a microservice. Let's actually run microservices.

Start task management service

Enter the following three commands in order. The task management service port number is 8080.

$ git clone https://[email protected]/MasayaMizuhara/taskmanagement.git
$ cd taskmanagement
$ ./gradlew bootRun

Start the report service

Enter the following three commands in order. The port number for the reporting service is 8082.

$ git clone https://[email protected]/MasayaMizuhara/taskreport.git
$ cd taskreport
$ ./gradlew bootRun

Launch Elasticsearch

Execute the following command.

$ elasticsearch

Start Logstash

Execute the following command. Change the location and name of the configuration file according to your environment.

$ sudo logstash -f /usr/local/etc/logstash/logstash-sample.conf

Start Kibana

Execute the following command.

$ kibana

Task registration

Execute the following command to register the task in the task management service.

$ curl -D - -H "Content-type: application/json" -X POST -d '{"title":"Task title", "summary":"Task summary"}' http://localhost:8080/task/management/register

Check progress

Execute the following command to get the progress from the reporting system.

$ curl http://localhost:8082/task/report/progress

Check the log

Execute the command to check the progress again with the task management service stopped.

$ curl http://localhost:8082/task/report/progress

If you check the log after executing the above command 6 times in a row, you can confirm that the following log is output.

app.log


08:16:40.335 [http-nio-8082-exec-6] ERROR com.example.taskreport.application.TaskReportService - Hystrix circuit short-circuited and is OPEN

This corresponds to the state where the connection between the reporting service and the task management service cannot be confirmed and the circuit breaker is tripped.

Access [Kibana](http: // localhost: 5601) from your browser and perform the following operations.

Kibanaの操作(1)

Then, the following operations are performed.

Kibanaの操作(2)

This completes the index pattern creation. If you select Discover in the left menu, you can see that the log is output.

Summary

In this article, we prepared a simple monolith application and made it a microservice according to the idea introduced in the book. This time it wasn't too complicated because the number of microservices was small and the scale was small (I was able to forgo the introduction of the event data pump, etc.), but it was introduced in the book Online fashion retailers In an environment where more than 450 microservices such as Gilt are running, there are advantages such as ease of deployment, but the disadvantage is that the architecture becomes complicated. You need to face each other.

Since the method of linking between microservices changes depending on how the boundaries of services are set, it is important to set the boundaries of services appropriately and keep the linking between microservices as simple as possible in order to succeed in microservices. ..

Reference material

--Microservices Architecture

-Introduction to ElasticStack6 starting with ELK (Elasticsearch, Kibana, Logstash)

-Eric Evans Domain Driven Design

-Thorough introduction to Spring Java application development with Spring Framework

Recommended Posts

I tried to make the sample application into a microservice according to the idea of the book "Microservice Architecture".
I tried to make the "Select File" button of the sample application created in the Rails tutorial cool
I tried to make a sample program using the problem of database specialist in Domain Driven Design
I tried to make a client of RESAS-API in Java
[Java] I tried to make a maze by the digging method ♪
I tried to make a machine learning application with Dash (+ Docker) part2 ~ Basic way of writing Dash ~
I tried to make a parent class of a value object in Ruby
I tried to make a simple face recognition Android application using OpenCV
[iOS] I tried to make a processing application like Instagram with Swift
I tried to make full use of the CPU core in Ruby
I tried to make a talk application in Java using AI "A3RT"
Make a margin to the left of the TextField
I tried using the profiler of IntelliJ IDEA
I tried to make a machine learning application with Dash (+ Docker) part3 ~ Practice ~
What I tried when I wanted to get all the fields of a bean
I tried to clone a web application full of bugs with Spring Boot
[Small story] I tried to make the java ArrayList a little more convenient
I tried to summarize the state transition of docker
I tried to decorate the simple calendar a little
05. I tried to stub the source of Spring Boot
I tried the new feature profiler of IntelliJ IDEA 2019.2.
I tried to make a login function in Java
I wrote a sequence diagram of the j.u.c.Flow sample
[Ruby] I want to make a program that displays today's day of the week!
I tried to create a log reproduction script at the time of apt install
I tried to make a message function of Rails Tutorial extension (Part 1): Create a model
A memorandum because I was addicted to the setting of the Android project of IntelliJ IDEA
I tried to make a product price comparison tool of Amazon around the world with Java, Amazon Product Advertising API, Currency API (2017/01/29)
I tried to make a web application that searches tweets with vue-word cloud and examines the tendency of what is written in the associated profile
I tried to make an application in 3 months from inexperienced
I tried to modernize a Java EE application with OpenShift.
I tried to summarize the basics of kotlin and java
I want to make a specific model of ActiveRecord ReadOnly
[Swift] I tried to implement the function of the vending machine
I tried to develop a web application from a month and a half of programming learning history
I tried JAX-RS and made a note of the procedure
I tried using Hotwire to make Rails 6.1 scaffold a SPA
I tried to touch the asset management application using the emulator of the distributed ledger Scalar DLT
I tried to summarize the basic grammar of Ruby briefly
I tried to build the environment of WSL2 + Docker + VSCode
[VBA] I tried to make a tool to convert the primitive type of Entity class generated by Hibernate Tools to the corresponding reference type.
[Java] I tried to make a rock-paper-scissors game that beginners can run on the console.
I tried to develop the cache function of Application Container Cloud Service in the local environment
I tried to make a message function of Rails Tutorial extension (Part 2): Create a screen to display
I tried to implement a buggy web application in Kotlin
I tried to make it possible to set the delay for the UDP client of Android by myself
[Rails] Implementation of multi-layer category function using ancestry "I tried to make a window with Bootstrap 3"
I tried to express the result of before and after of Date class with a number line
After learning Progate, I tried to make an SNS application using Rails in the local environment
I tried to take a look at the flow of Android development environment construction with Android Studio
I tried incorporating the principle of a single file component into a rails 6 app. I'm not sure.
Try to imitate the idea of a two-dimensional array with a one-dimensional array
I tried to solve the problem of "multi-stage selection" with Ruby
I tried to illuminate the Christmas tree in a life game
I tried running a letter of credit transaction application with Corda 1
I tried to build the environment of PlantUML Server with Docker
I tried using the cache function of Application Container Cloud Service
[Unity] I tried to make a native plug-in UniNWPathMonitor using NWPathMonitor
I made a gem to post the text of org-mode to qiita
I tried to build a simple application using Dockder + Rails Scaffold
I tried to make an Android application with MVC now (Java)