JavaRush /Java Blog /Random EN /Java Microservices Guide. Part 1: Microservices Basics an...

Java Microservices Guide. Part 1: Microservices Basics and Architecture

Published in the Random EN group
In this tutorial, you will learn what Java microservices are, how to design and build them. It also touches on questions about Java microservice libraries and the appropriateness of using microservices. Translating and Adapting Java Microservices: A Practical Guide .

Java Microservices: The Basics

To understand microservices, you must first define what they are not. But “monolith” is not them - Java monolith: what is it and what are its advantages or disadvantages? Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 1

What is a Java Monolith?

Imagine that you work for a bank or a fintech startup. You provide users with a mobile app that they can use to open a new bank account. In Java code, this will result in a controller class. Simplified, it looks like this:
@Controller
class BankController {

    @PostMapping("/users/register")
    public void register(RegistrationForm form) {
        validate(form);
        riskCheck(form);
        openBankAccount(form);
        // etc..
    }
}
You need the controller to:
  1. Confirmed the registration form.
  2. Checked the risks against the user's address to decide whether to provide him with a bank account.
  3. Opened a bank account.
The class BankControllerwill be packaged along with the rest of your sources into a bank.jar or bank.war file for deployment - this is the good old monolith that contains all the code you need to run your bank. As a rough estimate, the initial size of the .jar (or .war) file will be between 1 and 100 MB. Now you can just run the .jar file on your server... and that's all you need to do to deploy your Java application. Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 2Picture, top left rectangle: Deploying a mono(cast) bank java -jar bank.jar (cp .war/.ear into appserver). Right rectangle: open browser.

What is the problem with Java monoliths?

At its core, there is nothing wrong with Java monoliths. However, experience has shown that if you have in your project:
  • Many programmers/teams/consultants work...
  • ... over the same monolith under pressure from customers with very vague requirements ...
  • within a couple of years...
... then in this case your small bank.jar file turns into an immense gigabyte of code alone, which is even scary to approach, let alone deploy.

How to reduce the size of a Java monolith?

A natural question arises: how to make the monolith smaller? Now your bank.jar is running on one JVM, one process on one server. No more, no less. And right now, a logical thought may come to mind: “But the risk check service can be used by other departments in my company! It has nothing to do with my monolithic banking application! Perhaps it should be cut out of a monolith and deployed as a separate product? That is, technically speaking, run it as a separate Java process.”

What is a Java microservice?

In practice, this phrase means that now the method call riskCheck()will not be made from BankController: this method or bean with all its auxiliary classes will be moved to its own Maven or Gradle project. It will also be deployed and placed under version control independent of the banking monolith. However, this entire extraction process does not turn your new RiskCheck module into a microservice per se, since the microservice definition is open to interpretation. This leads to frequent discussions within teams and companies.
  • 5-7 classes in the project - is it micro or what?
  • 100 or 1000 classes... still micro?
  • Microservice generally related to the number of classes or not?
Let's leave the theoretical reasoning, and instead stick to pragmatic considerations and do this:
  1. Let's call all individually deployed services microservices, regardless of their size or domain boundaries.
  2. Let's think about how to arrange interservice communication. Our microservices need ways to communicate with each other.
So, let's summarize: before, you had one JVM process, a solid monolith for the bank to work. You now have a bank monolith JVM process and a separate RiskCheck microservice that runs within its own JVM process. And now your monolith needs to call this microservice to check the risks. How to do it?

How to establish communication between Java microservices?

In general, there are two options - synchronous and asynchronous communication.

Synchronous communication: (HTTP)/REST

Typically, synchronized communication between microservices is done through HTTP and REST-like services that return XML or JSON. Of course, there may be other options - take at least Google Protocol Buffers . If you need an immediate response, it's better to use REST communication. In our example, this is exactly what you need to do, since a risk check is required before opening an account. If there is no risk check, there is no account. The tools will be discussed below, in the section “ Which libraries are best suited for synchronous Java REST calls ”.

Messaging - asynchronous communication

Asynchronous microservice communication is typically done through messaging with a JMS implementation and/or using a protocol such as AMQP . We wrote “usually” here for a reason: let's say the number of email/SMTP integrations cannot be underestimated. Use it when you don't need an immediate response. For example, the user clicks the "buy now" button, and you in turn want to generate an invoice. This process, of course, should not occur within the user's purchase request-response cycle. Below we will describe which tools are best suited for Java asynchronous messaging .

Example: REST API call in Java

Suppose we have chosen synchronous microservice communication. In this case, our Java code (the one that we cited above) at a low level will look something like this. (By low-level here we mean the fact that microservice communication typically creates client libraries that abstract you from the actual HTTP calls).
@Controller
class BankController {

    @Autowired
    private HttpClient httpClient;

    @PostMapping("/users/register")
    public void register(RegistrationForm form) {
        validate(form);
        httpClient.send(riskRequest, responseHandler());
        setupAccount(form);
        // etc..
    }
}
Based on the code, it becomes clear that we now need to deploy two Java (micro) services, Bank and RiskCheck. As a result, we will have two JVM processes running. Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 3That's all you need to develop a project with Java microservices: just build and deploy smaller chunks (.jar or .war files) instead of one monolithic one. It remains unclear the answer to the question, how should we cut the monolith into microservices? How small should these pieces be, how to determine the correct size? Let's check.

Java Microservices Architecture

In practice, companies develop microservice projects in different ways. The approach depends on whether you are trying to convert an existing monolith into a project with microservices, or whether you are starting a project from scratch.

From Monolith to Microservices

One of the most logical ideas is to extract microservices from an existing monolith. Note that the "micro" prefix here doesn't really mean that the extracted services will be really small, it doesn't have to be. Let's look at the theoretical foundations.

Idea: break the monolith into microservices

A microservice approach can be applied to legacy projects. And that's why:
  1. Most often, such projects are difficult to maintain / change / expand.
  2. Everyone, from developers to management, wants to simplify.
  3. You have (relatively) clear domain boundaries, meaning you know exactly what your software is supposed to do.
Going back to our example, this means you can take a look at your banking Java monolith and try to break it down along domain boundaries.
  • So, it would be reasonable to single out the processing of user data (such as names, addresses, phone numbers) into a separate "Account Management" microservice.
  • Or the aforementioned "Risk Checker Module" which checks the user's risk levels and can be used by many other projects or even company departments.
  • Or an invoicing module that sends invoices in PDF format or by mail.

Implementation of the idea: let someone else do it

The approach described above looks great on paper and UML-like diagrams. However, everything is not so simple. For its practical implementation, serious technical preparation is needed: the gap between the understanding of what would be nice to extract from the monolith and the extraction process itself is huge. Most enterprise projects get to the point where developers are afraid to, say, upgrade from a 7 year old version of Hibernate to a newer one. Libraries will be updated along with it, but there is a non-illusory danger of breaking something. And so, the same developers now have to dig into ancient legacy code with obscure database transaction boundaries and extract well-defined microservices? More often than not, this problem is very difficult and cannot be “solved” on the whiteboard or in architecture meetings. Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 4To quote Twitter developer @simonbrown: I'll repeat this over and over... if people can't build monoliths properly, microservices won't help. Simon Brown

Project from scratch based on microservice architecture

In the case of new Java projects, the three numbered items from the previous part look a little different:
  1. You start with a clean slate, so there is no “baggage” to maintain.
  2. The developers would like to keep things simple in the future.
  3. Problem: you have a much more nebulous picture of domain boundaries: you don't know what your software is actually supposed to do (hint: agile;))
This leads companies to try new projects with Java microservices.

Technical microservice architecture

The first point seems to be the most obvious for developers, but there are those who strongly discourage it. Hadi Hariri recommends the "Extract Microservice" refactoring in IntelliJ. And although the following example is very simplified, the implementations observed in real projects, unfortunately, have not gone too far from it. Before microservices
@Service
class UserService {

    public void register(User user) {
        String email = user.getEmail();
        String username =  email.substring(0, email.indexOf("@"));
        // ...
    }
}
With Java substring microservice
@Service
class UserService {

    @Autowired
    private HttpClient client;

    public void register(User user) {
        String email = user.getEmail();
        //теперь вызываем substring microservice via http
        String username =  httpClient.send(substringRequest(email), responseHandler());
        // ...
    }
}
So you are essentially wrapping a Java method call in an HTTP call, for no apparent reason to do so. One of the reasons, however, is the following: lack of experience and an attempt to force a Java microservices approach. Recommendation: don't do it.

Workflow-Oriented Microservice Architecture

The next common approach is to split Java microservices into workflow-based modules. Real life example: In Germany, when you visit a (public) doctor, he must record your visit in his medical CRM system. In order to receive payment from the insurance, it will send data about your treatment (and the treatment of other patients) to an intermediary via XML. The broker will consider this XML file and (simplified):
  1. Check if the correct XML file was received.
  2. Check the plausibility of the procedures: say, a one-year-old child who received three brushing procedures in one day from a gynecologist looks somewhat suspicious.
  3. Combine XML with some other bureaucratic data.
  4. Send an XML file to the insurance company to initiate payments.
  5. And send the result to the doctor, providing him with the message "success" or "please send this record again as soon as it makes sense."
Note. In this example, communication between microservices does not play a role, but could well be done asynchronously by a message broker (for example, RabbitMQ), since the doctor still does not receive immediate feedback. Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 5Again, this looks great on paper, but legitimate questions arise:
  • Is it necessary to deploy six applications to process one XML file?
  • Are these microservices really independent of each other? Can they be deployed independently of each other? With different versions and API schemes?
  • What does the information plausibility microservice do if the verification microservice is not running? Is the system still running?
  • Do these microservices share the same database (they certainly need some common data in the DB tables), or do they each have their own?
  • … and much more.
Interestingly, the diagram above looks simpler because each service now has its exact, well-defined purpose. It used to look something like this dreaded monolith: Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 6Although one can argue about the simplicity of these diagrams, now you definitely need to solve these additional operational problems.
  • You don't just need to deploy one application, but at least six.
  • You may even need to deploy multiple databases, depending on how far you want to go into the microservice architecture.
  • You need to make sure that each system works online, and works normally.
  • You need to make sure that your calls between microservices are really resilient (see How to make a Java microservice resilient?).
  • And everything else that this setting implies - from local development settings to integration testing.
So the recommendation would be:
  • If you're not Netflix (most likely you're not Netflix)...
  • Unless you have super strong job skills where you open a development environment and it summons a chaos monkey that drops your production database which is easily restored in 5 seconds.
  • or you feel like @monzo and are ready to try out 1500 microservices just because you can.
→ Don't do this. And now less exaggerated. Trying to model microservices beyond domain boundaries seems like a perfectly reasonable thing to do. But that doesn't mean you have to take one workflow and break it down into tiny separate parts (get XML, validate XML, forward XML). Therefore, whenever you start a new project with Java microservices and the domain boundaries are still very vague, try to keep the size of your microservices low. You can always add more modules later. And make sure you have an advanced DevOps team/company/department to support your new infrastructure. Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 7

Polyglot or command-oriented microservice architecture

There is a third, almost libertarian approach to developing microservices: allowing teams or even individuals to implement user stories using any number of languages ​​or microservices (marketers refer to this approach as “polyglot programming”). So, the XML validation service described above can be written in Java, while the validation microservice can be written in Haskell at the same time (to make it mathematically sound). Erlang can be used for the insurance forwarding microservice (because it really needs to scale ;)). What might seem like fun from a developer's point of view (developing the perfect system with your perfect language in a sandbox) is actually never what the organization wants: homogenization and standardization. Java Microservices Guide.  Part 1: Microservices Basics and Architecture - 8History shows that standardization usually takes root too deeply. For example, developers at large Fortune 500 companies were sometimes not even allowed to use Spring because it was "not part of the company's technology plan." However, a complete transition to a polyglot approach is almost the same, the other side of the same coin. Recommendation: If you're going to use polyglot programming, try less variety in the same programming language ecosystem. So, it is better to use Kotlin and Java together (both languages ​​are based on the JVM and are 100% compatible with each other), rather than Java and, say, Haskell. In the next part, you will learn about deploying and testing Java microservices.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION