Everyone is building microservices. For the last 1.5 years, it was the dominant architectural style for all new software projects. And while a microservice architecture should make the implementation of each service easier, it also introduces new challenges.
That’s especially the case for the persistence layer. So, it’s no surprise that I had lots of discussions about implementing the persistence layer of a microservice. I also helped lots of teams who struggled with the consequences of the new architectural style. In this article, I want to summarize some of the discussions and provide answers to the most common questions.
Can you use Hibernate/EclipseLink/JPA in your microservice?
But before we start, let’s address the elephant in the room and answer one of the most heated discussion topics: Yes, JPA and all its implementations (Hibernate, EclipseLink, OpenJPA, …) can be valid options to implement the persistence layer of a microservice.
You shouldn’t ask if Hibernate is a good fit for a microservice architecture. You should ask if its a good fit for the use cases you need to implement.
Internally, microservices aren’t that different to monoliths. OK, they are much smaller, and communication between service can get incredibly complicated. But that most often doesn’t affect your persistence layer.
Microservices still need to store and read information. If you decide to store your data in a relational database, Hibernate might be a good option to implement your persistence layer. Before you make the decision, you should ask yourself:
- Do most of your use cases require record-centric CRUD operations? If that’s the case, JPA is a good option to implement your persistence layer.
- Do you need to implement mass insert or update operations? In this case, Hibernate’s object-relational mapping might create more problems than it solves. You should take a look at other frameworks, like jOOQ.
- Do you need to implement a lot of complex queries? You can do that using JPA’s native queries. But are other frameworks, like jOOQ, are better suited for these use cases. You should either use these or integrate them with Hibernate.
OK, for the rest of this article, let’s assume you mostly need to implement record-centric CRUD use cases. So, JPA and its implementations are a good fit for your microservice.
Basic rules for microservices
If you decide to build a system of microservices and take full advantage of the benefits of this architectural style, you need to follow a few basic rules:
- Each service has to have its own database. If you break that rule, you introduce dependencies between your services. That makes them harder to develop, deploy and operate.
- A service only knows its own database. You can’t define any entities, managed associations, queries, foreign key constraints, stored procedures or database trigger that use a table that belongs to another microservice. If you have to reference any information that’s managed by a different service, you can only store the id(s) of the associated object(s).
- Each service uses and controls its own transactions. There are no transactions that span over multiple services. Otherwise, your microservice system becomes hard to scale.
- Whenever you update your microservice, you need to update its database automatically. Flyway and Liquibase are powerful and easy to use frameworks to implement automated, version-based update procedures.
Sharing data between services
If you ever tried to implement a system of microservices, you know that the aforementioned rules are much easier to proclaim then to implement. Especially rule 2, and 3 can be hard to fulfill if you need to implement complex business logic.
The easiest and often recommend approach to avoid these problems is to design a self-sustained service which manages all the information they need. While I agree with that recommendation, I also know that it’s not always possible to do that. There might be good reasons to split something into multiple services, and sometimes new features just don’t fit well into the existing design. In these situations, you need to implement read or write operations that affect the data managed by different services.
Reading data from multiple services
There are 2 general ways to make sure that services stay independent of each other while performing read operations that require data managed by different microservices:
- API Composition: Your client application or another microservice integrates multiple services by calling their API and merging the results in memory.
- Create a view database: You create an additional microservice which implements its query on its own database. This database contains a copy of all the information that you need to implement your query. This is used in the CQRS pattern, but there are also other ways, e.g., change data capture, to share the information between multiple services. I will show you a few examples in future articles.
Updating data in multiple services
And if you need to implement a write operation that affects multiple microservices, you can use the SAGA pattern and split the operation into multiple API calls. Each of these API calls uses a local transaction. Because you can’t rely on a distributed business transaction, you need to execute compensating operations whenever one of the API calls fails. These compensating operations need to undo all changes performed by the previous operations.
As long as the required use cases are a good fit for an object-relational mapping framework, there shouldn’t be anything holding you back from using Hibernate, EclipseLink or any other JPA implementation to create your microservice. Most of the challenges you need to solve while implementing your persistence layer, are caused by the architectural style and not by JPA.
That clearly shows that there is no free lunch in software development. If you want to benefit from the advantages of a microprofile architecture, you also need to handle its downsides. One of the biggest challenges is querying data from multiple services and ensuring data consistency during write operations that affect multiple services. In the next articles, I will get into more details on the patterns and possible solutions to these challenges.