JavaRush /Java Blog /Random EN /A Guide to Java Microservices. Part 1: Microservices Basi...

A Guide to Java Microservices. Part 1: Microservices Basics and Architecture

Published in the Random EN group
In this guide, you will learn what Java microservices are, how to design and create them. It also covers questions about Java microservice libraries and the feasibility of using microservices. Translation and adaptation of Java Microservices: A Practical Guide .

Java Microservices: Basics

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

What is a Java monolith?

Imagine 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 the presence of 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 of 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 a good old monolith containing all the code needed to run your bank. As a rough estimate, the initial size of a .jar (or .war) file will range from 1 to 100 MB. Now you can simply run the .jar file on your server... and that's all you need to do to deploy your Java application. A Guide to Java Microservices.  Part 1: Microservices Basics and Architecture - 2Picture, top left rectangle: deployment of a mono(lithic) bank java -jar bank.jar (cp .war/.ear into appserver). Right rectangle: open browser.

What's the problem with Java monoliths?

There is nothing inherently wrong with Java monoliths. However, experience has shown that if in your project:
  • There are many programmers/teams/consultants working...
  • ...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 scary to even approach, let alone deploy.

How to reduce the size of a Java monolith?

A natural question arises: how to make the monolith smaller? Right 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 verification service can be used by other departments in my company! It's not directly related to my monolithic banking application! Perhaps it should be cut out of the monolith and deployed as a separate product? That is, technically speaking, running 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 component 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 independently of the banking monolith. However, this entire extraction process does not turn your new RiskCheck module into a microservice per se, since the definition of a microservice is open to interpretation. This leads to frequent discussions within teams and companies.
  • Are 5-7 classes in a project micro or what?
  • 100 or 1000 classes... still micro?
  • Is microservice generally related to the number of classes or not?
Let's leave theoretical reasoning behind, and instead stick to pragmatic considerations and do this:
  1. Let's call all separately 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, to summarize: previously you had one JVM process, a solid monolith for running the bank. Now you have a banking monolith JVM process and a separate RiskCheck microservice that runs within its own JVM process. And now, to check for risks, your monolith must call this microservice. How to do this?

How to establish communication between Java microservices?

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

Synchronous communication: (HTTP)/REST

Typically, synchronized communication between microservices occurs via 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 is better to use REST communication. In our example, this is exactly what needs to be done, since risk verification is required before opening an account. If there is no risk checking, there is no account. We will discuss the tools below, in the section “ Which libraries are best for synchronous Java REST calls ”.

Messaging - Asynchronous Communication

Asynchronous microservice communication is typically accomplished by exchanging messages 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 certainly should not occur within the user's purchase request-response cycle. Below we will describe which tools are best for asynchronous Java messaging .

Example: REST API call in Java

Let's assume we choose synchronous microservice communication. In this case, our Java code (the one we presented above) at a low level will look something like this. (by low level here we mean the fact that for microservice communication, client libraries are usually created 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. A Guide to Java Microservices.  Part 1: Microservices Basics and Architecture - 3That's all you need to develop a Java microservices project: just build and deploy smaller chunks (.jar or .war files) instead of one monolithic one. The answer to the question remains unclear: 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 transform an existing monolith into a microservices project or starting the project from scratch.

From monolith to microservices

One of the most logical ideas is to extract microservices from an existing monolith. Note that the prefix "micro" here does not actually mean that the services extracted will be truly small; this is not necessarily the case. Let's look at the theoretical background.

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 simplification.
  3. You have (relatively) clear domain boundaries, meaning you know exactly what your software should do.
Returning to our example, this means that you can look at your Java banking monolith and try to break it down across domain boundaries.
  • So, it would be reasonable to separate the processing of user data (such as names, addresses, phone numbers) into a separate microservice “Account Management”.
  • Or the aforementioned "Risk Checker Module" which checks the user's risk levels and can be used by many other projects or even departments of the company.
  • Or an invoicing module that sends invoices in PDF format or by mail.

Implementation of an idea: let someone else do it

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

Project from scratch based on microservice architecture

In the case of new Java projects, the three numbered points from the previous part look slightly 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 fuzzier picture of domain boundaries: you don't know what your software is actually supposed to do (hint: agile ;))
This is leading to companies trying new projects with Java microservices.

Technical microservice architecture

The first point seems to be the most obvious for developers, but there are also 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, do not go 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 substring Java 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 obvious reason for doing so. One reason, however, is this: lack of experience and trying to force a Java microservices approach. Recommendation: Don't do this.

Workflow-oriented microservice architecture

The next common approach is to divide Java microservices into workflow-based modules. Real life example: In Germany, when you go to a (public) doctor, he must record your visit in his medical CRM system. To get payment from insurance, he will send data about your treatment (and the treatment of other patients) to the intermediary via XML. The broker will look at this XML file and (simplified):
  1. Will check if the correct XML file is received.
  2. It will check the plausibility of the procedures: say, a one-year-old child who received three teeth cleaning procedures in one day from a gynecologist looks somewhat suspicious.
  3. Will combine XML with some other bureaucratic data.
  4. Will forward the XML file to the insurance company to initiate payments.
  5. And it will forward the result to the doctor, providing him with the message “success” or “please resend this recording 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 (e.g. RabbitMQ), since the doctor does not receive immediate feedback anyway. A Guide to Java Microservices.  Part 1: Microservices Basics and Architecture - 5Again, this looks great on paper, but natural questions arise:
  • Is it necessary to deploy six applications to process one XML file?
  • Are these microservices truly 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 doesn't work? Is the system still working?
  • 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 above diagram looks simpler because each service now has its own precise, well-defined purpose. It used to look something like this scary monolith: A Guide to Java Microservices.  Part 1: Microservices Basics and Architecture - 6While you can argue about the simplicity of these diagrams, you definitely now need to solve these additional operational challenges.
  • 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 microservices architecture.
  • You need to make sure that each system is online and working properly.
  • You need to make sure that your calls between microservices are truly resilient (see How to make a Java microservice resilient?).
  • And everything else that this setup implies - from local development settings to integration testing.
So the recommendation would be:
  • If you're not Netflix (chances are, you're not Netflix)...
  • Unless you have super strong work skills where you open the development environment and it causes a chaos monkey that throws away your production database which is easily restored in 5 seconds.
  • or you feel like @monzo and are willing to try out 1500 microservices just because you can.
→ Don't do this. And now it’s less hyperbolic. Trying to model microservices across domain boundaries seems quite reasonable. But this doesn't mean you need to take one workflow and split it into tiny individual parts (receive XML, validate XML, forward XML). Hence, 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 at a low level. You can always add more modules later. And make sure you have advanced DevOps in the team/company/division to support your new infrastructure.

Polyglot or team-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 call this approach “polyglot programming”). Thus, the XML validation service described above could be written in Java, while the validation microservice at the same time could be written in Haskell (to make it mathematically sound). For the insurance forwarding microservice, you can use Erlang (because it really needs to scale ;)). What may seem fun from a developer's point of view (developing the perfect system with your perfect language in an isolated environment) is, in fact, never what the organization wants: homogenization and standardization. This means a relatively standardized set of languages, libraries, and tools so that other developers can continue to support your Haskell microservice in the future when you move on to greener pastures. A Guide to Java Microservices.  Part 1: Microservices Basics and Architecture - 8History shows that standardization usually becomes too deeply ingrained. 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 roadmap." However, a complete transition to the polyglot approach is almost the same thing, the other side of the same coin. Recommendation: If you are going to use polyglot programming, try less variety within 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