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?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:
- Confirmed the registration form.
- Checked the risks of the user's address to decide whether to provide him with a bank account.
- Opened a bank account.
BankController
will 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. Picture, 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...
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 callriskCheck()
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 call all separately deployed services microservices, regardless of their size or domain boundaries.
- Let's think about how to arrange interservice communication. Our microservices need ways to communicate with each other.
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. That'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:- Most often, such projects are difficult to maintain/change/expand.
- Everyone, from developers to management, wants simplification.
- You have (relatively) clear domain boundaries, meaning you know exactly what your software should do.
- 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. To quote Twitter developer @simonbrown: I'll say this over and over again... if people can't build monoliths correctly, microservices won't help. Simon BrownProject from scratch based on microservice architecture
In the case of new Java projects, the three numbered points from the previous part look slightly different:- You start with a clean slate, so there is no “baggage” to maintain.
- The developers would like to keep things simple in the future.
- Problem: You have a much fuzzier picture of domain boundaries: you don't know what your software is actually supposed to do (hint: agile ;))
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):- Will check if the correct XML file is received.
- 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.
- Will combine XML with some other bureaucratic data.
- Will forward the XML file to the insurance company to initiate payments.
- 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.”
- 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.
- 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.
- 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.
GO TO FULL VERSION