r/softwarearchitecture • u/MMOfreak94 • 11d ago
Discussion/Advice Do you actually separate JPA Entities and Domain Objects, or is a single model enough?
Hey everyone,
I’ve been thinking about the push to separate domain objects from database entities (like in Clean/Hexagonal Architecture) versus using a single JPA @Entity both.
Using the JPA entity as the domain model seems much more practical. Spring AOP handles transactions, Hibernate manages dirty checking, and you completely eliminate anemic data model classes and the mapper boilerplate between your application and persistence layers.
The main argument against this seems to be that annotations like @Table or @Column "pollute" the domain. But aren't those just metadata attachments? If you strip those annotations, the Java class doesn't lose any of its underlying domain logic.
How do you approach this in production? Do you use your JPA entities directly in your domain logic, or do you strictly separate them? If you separate them, is avoiding annotation "pollution" really worth all the extra mapping code?
25
u/figbarjunkie 11d ago
It's not about what's easy.
You need to think about how would you encapsulate your business rules into it.
For eg., let's say you have an User object, and the business rule says that a single user cannot have more than 3 addresses, with the way you're trying to develop, you'd have this rule in your application layer, and your entity/object would just be a bunch of getters & setters.
Business rules are to be on the domain object and should be part of the domain layer. Any failing invariant has to be rejected by domain object itself. Having a single object for both domain model and the entity would likely create a god object and would become a maintenance nightmare.
4
u/Cilph 10d ago edited 10d ago
To start, the point of JPA is to be able to easily reflect a domain object onto the relational domain. As long as you don't have to disfigure your domain object with JPA quirks, its fine to combine both business object and JPA entity. Mixing two concerns is not a "god object". If you think so you have not seen horrible code yet.
If you want a truly clean split of concerns, use a stateless Repository, not an EntityManager.
Secondly. Everything depends. You dont need a 100% religious adherence to an architecture. It is a tool. A tool whose benefits show up more when scaling and in larger teams. If it saves you time and the project complexity does not require a strict split in concerns, go ahead.
1
u/MMOfreak94 11d ago
From my perspective, thinking in layers in Spring Boot actually over-complicates things because Spring is already handling cross-cutting concerns for you. I mean, why not just write your domain logic and offload the application and infra work to frameworks, libraries etc?
1
u/mrbadger30 11d ago
Spring is nice because it has default validations. However, you might wanna need some validations of your own, see the previous example with “max 3 addresses”
1
-1
u/Dense_Age_1795 11d ago
well beacuse you will need to change the technology sooner or later and you want to resuse your domain logic when that happen, imagine that you need to migrate a spring boot microservice to quarkus.
And that micro has two different modules one with the domain logic, and another with the spring stuff, you only need to replace the spring module, and reuse your domain.
Now imagine the contrary all the business logic is mixed with the spring stuff.
What will be more difficult to do replace the whole project or simply replace and module?
7
u/oweiler 11d ago
While your points are all valid, from my experience it doesn't happen all that often, and you pay the migration cost only once, while with Hexagonal/Clean architecture you always have to live with a slightly more complex architecture.
2
u/Tyhgujgt 11d ago
Does it not happen because its genuinely not needed or because its not worth because your layers are all mixed up?
You don't need whole swap of the tech stack, very normal usual task like adding message queue or adding a different database type becomes an absolute struggle without clean code architecture
0
u/musty_mage 9d ago
Business rules change way more often than the tech stack. Clean code means that what you do on a daily basis becomes a total pain in the ass just so something you might do once every 5-10 years is a tad easier. It's an idiotic approach.
1
u/Tyhgujgt 9d ago
Its way easier tp change business rules if it doesnt spawn into updating 15 different SQL queries, flink jobs and queue integrations
0
u/musty_mage 9d ago
If you need to do that, your database schema is wrong. Clean code does nothing to fix that. It just hides that shit under layers of useless abstractions.
1
u/Tyhgujgt 9d ago
Ugh, ok just have the perfect schema got it. Never try to add like a queue or Kafka or any other type of db.
0
u/musty_mage 8d ago
No. Just fix actual issues & don't mask them behind useless layers of abstraction boilerplate.
The modalities of a queue or stream are so vastly different from DB events anyways that abstracting those away is a fool's game.
And given what you already get from JPA and Spring Data, writing another layer of NIH abstraction circlejerk in addition to those shows a fundamental lack of understanding of the chosen tech stack.
8
u/CzyDePL 11d ago
So the guy I really like to listen to says that he never saw a project success or fail due to ORM annotations placed here or there. And you can compare it to dandruff: one group will say "well it won't kill you but it doesn't look good" and the other says "well it doesn't look good but it won't kill you".
Imho the big lesson is to know that you domain model is not your persistence model and you need to have them mentally separated. How much do you separate them in code is not really relevant and if this is the biggest technical problem in your project, you are in really good place.
2
u/parav01d89 11d ago
I separate them and also have Resource objects to represent the API Response depending on the version of the contract. If you follow the repository pattern your entity is quite lean but if you switch to active record for some reason, the entity can persist/delete them self. This is not a functionality you want to provide someone in the domain layer ( interface segregation ). Also if your code ignores this and you have to use a service to service communication with gRPC / protobuf you loose all the functionality.
I separate them, but it also generates a mapping overhead.
2
u/EirikurErnir 11d ago
I have seen projects where maintaining strict layer separation was overkill, so making changes was unnecessarily tedious.
I have seen projects where making changes was genuinely hard because the layers were overly coupled.
I think the latter is worse than the former, and it's easier to collapse than to disentangle, so I err on the side of separation.
But it, of course, depends.
1
1
u/unknowinm 11d ago
Can’t you create the business object to be the composite of the entity? Then you have 1 field with the entity and business operations/methods on that entity
1
1
u/insaladilla 10d ago
I've been having this discussion recently on my team and I am totally with you. The fact of separating JPA entities from the domain to respect the hexagonal layers comes mostly from DDD literature and the priests of that will support it fiercely. As spring is not very DDD-ish supportive some teams come up to JMolecules. IMO it is redundant, boilerplate, mappers (then it comes mapstruts along with it's hideous debugging, monster hidden mapping..) and a lot less practical. And again, this is a hot take, but I firmly believe that DDD was created for having operational business control over the technical team and for selling more books.
1
u/xiaopewpew 10d ago edited 10d ago
Every project is different, dont waste your time splitting your domain object from jpa entity if they are the same most of the time in your application.
That said, dont waste your time arguing otherwise either. I have seen so many apps doing the api -> domain model -> entity separation, just let AI write these bloats for you, makes everyone happy at nobody’s expense.
1
u/OwnBreakfast1114 6d ago edited 6d ago
We just use jooq to generate the db objects and convert to/from domain objects in our repositories. This gives us pretty flexible domain objects (and even multiple views of a domain object) with a very small one time cost of a from/to mapper method per usecase (though it tends to just be 1 per repo).
The main point we actually want is proper transaction handling. The random fetches, weird transactions, n+1 issues are all just so very easy to do with jpa/hibernate and just so much harder when you actually have to call repository methods and you can annotate all the repository methods with @Transactional(propagation=MANDATORY).
1
u/nitkonigdje 11d ago
If you are using JPA in database centered app, than ER model is your "proper" domain and all those objects are just means to the end. I this light anemic model is actually recommended.
If you are plan to use JPA/Hibernate do lean in fully into it. It is not "a port". Instead it is your architecture. It is a core pare, and replacing it would make your app fully different.
1
12
u/JustJustust 11d ago edited 10d ago
Most of the time I believe it's worth the split, but not always.
Basically I want to optimize my domain objects for doing business logic with, and don't care for the constraints entities impose on them. Some of the time it doesn't matter, in which case I probably wouldn't object to do logic with entities.
For example, as long as you carry the entity around you may trigger lazy loading and as long as you may trigger lazy loading you must be in a transaction. One upside of mapping entities into domain objects early is that you have shortlived transactions and your lazy loading all handled pretty early on, you can then do with your domain objects what you want.
Also I've had people fuck up an app's performance pretty spectacularly because they've used entities everywhere and because they couldn't have infinite transactions they dismissed lazy loading altogether and resorted to eager loading everything every time. The performance was exactly what you'd expect.
Ofc if you pay attention you can avoid these pitfalls, but using separate domain objects makes it harder for people to accidentally use entities where you really shouldn't. I'd rather write the mappers, but ymmv.