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?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:
- Confirmed the registration form.
- Checked the risks against 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 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. Picture, 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...
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 callriskCheck()
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 call all individually 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, 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. That'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:- Most often, such projects are difficult to maintain / change / expand.
- Everyone, from developers to management, wants to simplify.
- You have (relatively) clear domain boundaries, meaning you know exactly what your software is supposed to do.
- 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. To quote Twitter developer @simonbrown: I'll repeat this over and over... if people can't build monoliths properly, microservices won't help. Simon BrownProject 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:- 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 more nebulous 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 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):- Check if the correct XML file was received.
- 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.
- Combine XML with some other bureaucratic data.
- Send an XML file to the insurance company to initiate payments.
- 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."
- 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.
- 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.
- 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.
GO TO FULL VERSION