The article is a long read. I recommend you to look through the Table of contents in advance. Perhaps some parts can be more intriguing than others...
For further actions, you may consider blocking this person and/or reporting abuse
Nice explanation about the theme, but considering tradeoffs of rich domain with hibernate orm, don´t be better goes to a concentric archictecture(clean arch, hexagonal, etc) directly?
Sounds like a lack of single responsability, mixing domain rules with persistent data operations.
@kauan_amarante In my point of view, Rich Domain Model with Hibernate is the same thing as hexagonal architecture. Consider such Hibernate entity:
As you can see,
Pocket
entity is a regular Java class. There are no persistent data operations. You can verifyPocket
operations with simple unit tests.Annotations are just hints but not executable code. So, Hibernate treats them as something that should be persisted. But this implementation is hidden behind the scenes.
If you try to go for canonical hexagonal architecture, you will have two options:
Implement your own Hibernate-like framework
Suppose that
Pocket
is a simple Java class with no Hibernate annotations. Look at the code snippet below:There are no much difference from Hibernate entity (except that this class has only all-args constructor). At the same time, you still have to translate entity changes to the database. Therefore, you would have to implement your own dirty checking mechanism. And this is a really complicated task.
I understand that Hibernate's dirty checking algorithm can be not so efficient. But it's generic for each scenario. You can write something that suits your requirements better. However, such infrastructure code will increase the complexity of your application severely. And most likely you would want to move this logic to a separate project (i.e. create your own Hibernate).
Create separate domain and Hibernate classes.
Theoretically you could have two classes
Pocket
andPocketEntity
. The first one is the domain class (look at the example in the previous paragraph). The second one is the Hibernate entity. In this case, persistence layer interacts withPocketEntity
and the business logic touches onlyPocket
.I've seen some examples on the Internet. But to be honest, I don't see any value with this approach.
Pocket
andPocketEntity
are most likely identical. So, you would need code which responsibilty is dumb mappingPocketEntity
toPocket
and vice-versa. Why would you need this if you can just interact with singlePocket
directly?I understand your point of view, it is all about "Create separate domain and Hibernate classes." and I think your domain is a little bit "simple" yet, database persistence entity and domain entity are different things, one cannot depends each other. A simple example is: for your database record you need something that identifies(like PK) but for your domain entity it's irrelevant, meaning the PK exists only in database context.
I think that way you can achieve the single responsibility principle and increase manutenability, even that is only hibernate "annotations" it's different concerns.
An entity describes its behaviour by public methods but not the fields. If you need database PK which is not part of the contract, you can make it a private field and don't expose it with a getter. Hibernate can work with private fields that have no getters or setters.
don't really look like a hexagonal architecture
more info
reflectoring.io/spring-hexagonal/
anyway, with microservice way, anemic seem the way to go
with mapstruct, spring data, lombok a lot of code showed in this article don't need to be wrote
Amazing article, thanks! I could really learn a lot from it
Some points I'd also think is worth mentioning:
Chapter: Don't add setters, getters, and public no-args constructor -> Don't add LOMBOK setters, getters, and public no-args constructor
Creating setters and getters (even lombok getters and setters) is generally fine if the set / get of the attribute has no rules associated with it (yet) - You can override it later on, I kind of disagree with the
changeXXX
method approach and would do the validation inside the public setter itself, but it's really more secure than a default setter without the business logic, and I think that's the main point in here.Another point of getters: Be careful when "getting" a list, make sure you give the user a copy of the list, not the list itself, otherwise, the person will be able to avoid business rules set on the
setList
method.Also, be careful when adding builders, by standard, lombok builders will not protect your entity, however, there are some alternatives:
You can make sure your
.builder()
method that provides you anentityBuilder
receives as parameters the required fields, or uselombok.NonNull
, which I personally don't like, since it's a runtime check.Thanks for the articles, I started reading from the end and missed something. It is correctly noted that the rich model is rarely used in real projects; mostly I see a transactional script. And often developers do not understand how and why to use Hibernate, especially the relationships between entities and this is a pain.
Thank you for such meticulous and thorough research. I have a question.
The code from the "performance implication section" heavily relies on the encapsulated implementation details from the
Pocket
aggregate. The service knows precisely what needs to be prefetched.We still preserve the invariant, but this solution is arguably more cognitively complex than the option to give up on implementing the aggregate and preserving the invariant within the boundaries of a service method instead.
I mean, it's a tradeoff. Aggregates tend to cause performance issues, so it's easy to use them when it's possible to fetch them eagerly into the memory without even bothering with lazy-load scenarios. Using a service seems less of a hustle if I can't do it.
@maxarshinov
The short answer is 'it depends'.
There are no ultimate solutions in software engineering. Of course, you can put some logic within a service and remove OneToMany relation at all. But that would make your model less rich and more anemic.
The idea of Rich Domain Model is to make the model as solid as possible. Therefore, the service layer contains no business logic (or few). On the contrary, the Anemic Domain Model advocates to declare entities as dummy DTOs and put all business operations inside service layer.
In my opinion, Rich Domain Model is easier to read and understand (btw, my colleagues also agree with me). But I'm just a software engineer and not a genius :) So, this article describes my opinion upon this topic and nothing more.
I can say that putting the logic inside the service layer has no harm by itself. If you do something on purpose (and you know why), then you're good to go. The major criteria of any software product is maintainability. If your way makes it better, then it's a good pattern in your case. But it also can be a poor solution in other teams. So, yeah, it depends.
I also would like to
@ygmarchi
I think it's better to use the strategy pattern here. For example, you can pass an interface as a parameter:
In this case,
PaymentGateway
is an interface. You can test the behavior with a mock or stub implementation.In my opinion, entities are part of business logic not the queries. Therefore, entities represent CREATE/UPDATE/DELETE operations. If you want to combine your entities as views, there are options: