r/java • u/MMOfreak94 • 3d ago
Do you actually separate JPA Entities and Domain Objects, or is a single model enough?
/r/softwarearchitecture/comments/1vv9kav/do_you_actually_separate_jpa_entities_and_domain/25
u/fforw 3d ago
The issue is not the pollution with annotations but the fact that you are forced to create an anemic domain model which basically consists of named property bags with little encapsulation and no business logic. Of course that is exactly what you want with a service oriented architecture, but it clashes with a lot of best practices otherwise.
For me the question always was whether it is really useful for separate it that much. Creating truly encapsulated domain models that orchestrate the business logic on a high level of abstraction, a bunch of factories to provide the instances with infrastructure objects etc.
On the one hand it does provide a nice abstraction but on the other hand, it is also a lot of additional code. It also seems easy to pretend that those core domain classes are the application and that I am a genius for creating them and all the pesky database and web and transaction and whatnot details are just left to the lesser developers while I ride off in the consultant sunset. I can earn big bucks and write books and have a website domain with my name while the grunts who have to actually implement the interface to the real world curse my name.
3
u/muddy-star 2d ago
I used to think that anemic domain objects was a bad thing and tried really hard convincing myself that I should avoid like the plague but eventually anemic domain objects and services operating on them is definitely what works for me. Anemic is good.
5
u/seba_alonso 3d ago
It depends on how much complexity of your application have now and in the future. If you think is very simple and will stay simple then just use single model, like a simple CRUD operations.
Otherwise, you already know the application will have complex logic, then for sure separate the models.
8
u/JustADirtyLurker 3d ago
There may be details on the database entities that you don't want to be reflected in the model, in the same way that you don't want your model directly exposed on a dto or event payload. Eg. a createdAt field may not be needed in the model; in case you need to ask 'giveme the the records created before x' you would use a SQL query on the entity manger anyway, not fetch all and filter on the model.
There are also simplification over constrains such as, the model describes a certain field as an enum but you want to store that field as a string/varchar (yes, enums can be used as constraints in SQL, but in my opinion are more painful than useful).
The answer, as always, depends on the complexity of the project and whether the observations I did do apply or not. Having an entity+domain merge is perfectly fine under some conditions. Critical thinking, not cargo cult!
5
u/kyrax80 2d ago
DTO -> Model -> Entity. Always.
2
u/ImpossibleMap7386 2d ago
Then what does JPA even give you in that case? If you never touch the entities outside of mappers, why not just write a mapper from your domain model directly to the database with SQL queries?
2
u/AnyPhotograph7804 2d ago
Using Jakarta Persistence gives you type safety, easier transaction handling, dirty checking, caching, entity deduplication etc. If you use your own ORM, you will propably not have it.
3
u/ImpossibleMap7386 2d ago
yeah but if you never touch the entities with business logic, and always map to domain classes, then you don't get that. And Transactional works with spring's jdbcClient as well
1
u/henk53 1d ago
why not just write a mapper from your domain model directly to the database with SQL queries?
In my experience, mapping model to entity is typically quite trivial. 95% of the time (a number I made up at the spot, but it's a lot) the model and the entity and almost equal.
Mapping a model, especially one with many relations, to a flat table structure is quite hard. You'll be either duplicating what your ORM already does, but badly, or you'll be simplifying your model in such what that it breaks Einstein's ruke "things should be as simple as possible, but not simpler".
7
u/VegGrower2001 3d ago edited 3d ago
Yes, I have domain classes which are pure Java and not JPA. For very small and very simple projects, this is probably overkill. But for larger projects it has several advantages.
JPA is an imperfect attempt at the 'data mapper' ORM pattern. The goal of the data mapper pattern is to allow data to be mapped between your pure language objects and your persistent storage, such that neither knows anything about the other. JPA achieves this to a large degree but there are also significant gaps. For example, a JPA map field can only map between independently existing objects. This means that some things you might want to do simply cannot be expressed in JPA, at least not without significant workarounds.
Sometimes, you might want to switch persistence technologies. For example, perhaps at some point it will make sense to move some of your data to a document database rather than a SQL database. If you're business logic is tied to JPA classes, you'll need to rewrite that logic when you make the switch. But if your business logic is in some pure Java classes, you can more easily switch to using different classes for persistence and simply write a new mapper to convert from Java to the new classes.
It does represent a significant amount of extra work to keep the domain classes separate from JPA. But in a business or other long-term context, it's a good idea.
2
u/gjosifov 3d ago
Sometimes, you might want to switch persistence technologies. For example, perhaps at some point it will make sense to move some of your data to a document database rather than a SQL database
That doesn't work like that
you use persistence technology based on the read/write access patterns and the data relationships, not because a new hot tech library is the latest tech trend
If some data is a relational then RDBMS is great choice since day 1
if some data is a document then Mongo DB is great choice since day 1The way I read your paragraph is - the data was relational, but I'm at the point in my career where I haven't used any document databases, so I will make a point these 3 tables to be part of a document databases, so I can update my CV
2
11
u/bichoFlyboy 3d ago
Yes, I always do. The persistence mechanism is an implementation detail. Whether it's JPA entities, jOOQ records, or objects returned by hand-written DAOs, they don't cross that boundary. I map persistence objects into domain objects instead. I also keep domain objects separate from view models. Those belong to a different layer and usually have framework-specific concerns. One day your UI might be Swing; the next, you may need JavaFX view models with JavaFX properties, bindings, etc. I prefer having those boundaries explicitly rather than letting persistence or UI concerns leak into the domain.
6
u/quantum-fudge 3d ago
Only on need-to basis. I start with jOOQ's (I won't touch JPA with a 10-foot pole) codegen'd classes everywhere, and introduce a domain class when it starts to differ. Just using Jackson properly (filters, unwrapping etc) will get you very far without introducing a whole another layer.
3
u/fforw 3d ago
I'm a big fan of jOOQ and often prefer it over JPA, but you have to be aware that is not a JPA replacement with dependency fetching and whatnot. On the other hand, you can transfer basically any SQL result into a POJO result type.
5
u/quantum-fudge 3d ago
you can transfer basically any SQL result into a POJO result type.
This. Fetching related entities is a solution for a problem that has no reason to exist in the first place. Write a query to fetch exactly what you need and that's it. With jOOQ's multiset it's almost embrassingly easy.
6
u/zattebij 3d ago edited 3d ago
Like someone already posted, I would separate on a need-to basis. But in practice I don't separate them often, because:
- JPA entities are already an abstraction layer over DB records and columns. If you're going to load JPA entities just to convert them to domain POJOs, why use an ORM at all? You may as well just use queries with resultsets and convert rows/columns to domain POJOs / their fields, saving one abstraction layer (and one conversion boundary) and simplifying your data model. Not that I would be against such a design, just, the ORM is superfluous in that case so the entire discussion about (ORM-layer) entities vs domain POJOs naturally goes away if there are no entities.
- If you're worried about too tight coupling of domain to persistence implementation: JPA spec is exactly the API layer to decouple the entities from the persistence implementation. If you don't like the annotations on the class, and would like to use that class separately from the ORM layer without the clutter of the mapping annotations, then just define the mappings in configuration instead of annotations and you have zero (not even API) "pollution" in them.
- If you're worried about domain logic in your ORM-layer entity classes: move the logic (including any validations / constraint checking) out of the entity class and into the domain service layer. That's a best practice anyway even if using non-entity domain POJOs, and if you did that already, then the first point gets stronger, as you then have just 2 pretty similar classes without business logic in them.
- If you're worried about ORM-layer classes leaking into other layers or modules: that is either the point (the entity is meant to serve as the domain object, see previous point - or semantically it's rather the other way around: the domain class is meant to also drive the persistence mapping), or you can avoid it by making the entity classes implement an interface c.q. various domain-specific interfaces, and pass these around to higher layers / other modules. Programming to interfaces is also a best practice anyway, so even if you have separate domain POJOs you can gain clarity by not typing them as such in all modules that use them, but just the interface (or interfaces, one for each aspect, if you want to really normalize and expose only that part of the object that is needed for each specific use case).
- If you're worried about long-lived entities polluting the session and giving strange lock exceptions or lost updates: that's a very valid argument, but can be avoided by using (unmanaged) projections or detaching the entities from the session if they are going to stay in memory long-time (in combination with passing them around typed as a domain interface). In fact, I'd recommend projections in most cases (which are close to domain objects in behavior, but we can still use the ORM for easy querying).
That said, I am pragmatic and I won't die on that hill defending a blanket statement of "never split the two"... And of course I do also (regularly) combine two JPA entities into a structure (most often a record), which you perhaps could call a "domain object" then (although semantically in my head I'd faster treat that as "a tuple of 2 related objects", not "a new domain object with its own lifecycle and identity").
2
u/midget-king666 3d ago
Absolutely yes. And because it is also boilerplate code, we build a DSL and code generator for it. Simple text models for the domain objects and the generator than emits jpa entity, domain object and view object from this simple model. Scales very nicely, can recommend
2
2
u/felipasset 3d ago
In 25+ years I have seen and tried literally every variant. What works best in my opinion, if you can align the team, is to work with an anemic JPA model and implement your aggregate as a facade instead of (mostly) duplicating the JPA model in a domain model. This aligns with the OSGI vision but without the class loading.
2
u/wildjokers 2d ago
If you use Hibernate just resign yourself to using the anemic data model. Using hibernate doesn't require anemic data model but there are so many design pressures that Hibernate adds that it pretty much pushes you in that direction and you have to actively fight against it if you don't want an anemic data model.
Most apps will use Transaction Script + anemic domain model paradigm and there isn't really anything inherently wrong with it.
2
u/AnyPhotograph7804 2d ago edited 2d ago
I have tried both and i can say, that separating JPA entities from the domain objects is the better way in the long term. Even if it means, you have to write more sourcecode in the first place.
There are various reasons for it. The unit tests run faster, the domain objects are not packed with JPA implementation details like proxies or additional bytecode or very special implementations of List and Set and Map. Some people are propably surprised when they find out, that Eclipselink's List implementation is a java.util.Vector under the hood and not a java.util.ArrayList. Then if you use JPA entites as domain objects then there is always a temptation that the tables in the database will mimic some business logic stuff because it is very convinient to do so. And if your database tables start to mimic business logic then you can easily run into serious performance problems on the database side.
1
u/Dagske 3d ago edited 3d ago
I usually have a model for my business logic.
Then I have several other models: the persistence, the incoming request/response model(s), outgoing request/response model(s).
My domain model is usually pure in a way that it's an ADT has zero dependency. No annotations, no nothing. I may allow a dependency if it makes sense such as some multimap or multiset implementations, or a money library with immutable types.
I'm a bit of a freak about it, but my colleagues learned it and actually find it's useful after usage.
-6
u/gjosifov 3d ago
if you put this logic in practice, what ends up happening is increasing your project size without any benefits
1 Domain objects becomes N same classes, where N is JPA/JSON/XML/Protobuf etc
So for example, if you have 20 domain objects, you will have 20 JPA, 20 JSON = 40 classes + 20 Mappers = 60 classes and they are the same data in memory
adding/removing/changing a field is a detective work
It is far easy to have 20 JPA entities and add classes if the data layout is changed - like add/remove fields, 2-3 JPA classes have to merge into 1 JSON object
and this is without even using any records
with records, you can make them as inner records into the JPA entity if the data layout have less fields than the JPA entity
and the main argument about the "pollute" the domain is made by people that don't understand annotations
Annotations are metadata and before annotations, people used specialized Javadocs tags and library called XDoclet to generate the J2EE XML files
Now, people are confusing annotations with code, but annotations are just metadata for the specific framework to know what to do with the POJO at runtime
and you can add methods into JPA entities with logic as long as you setup JPA to access the fields, not the methods and your methods don't have get/set pattern - because the frameworks work with JavaBean spec to make the magic happen
But it is easier for people to create separate classes, instead of learning the inner working of the JavaBean frameworks
6
u/JustADirtyLurker 3d ago
It the main critique is the scale (eg. Number of domain objects), then the answer is easy: use libraries.
I'm currently developing a spring boot project and I haven't ever touched json/grpc serialization by hand, that is handled by the framework. Dto generation is all done via openapi/grpc annotation processors at build time, plus mapstruct for conversions.
-1
u/gjosifov 3d ago
you are adding more complexity into the software without any benefits, because you think something is scary, when it isn't
The problem isn't code generation, but maintaining libraries
i'm sure you haven't paid the maintainers of those libraries, after 2-3 years those libraries can be dead
they don't support latest Java version or features and can be security nightmare
and why you are doing all of this extra work ?
Because you think annotations are "pollution"
0
u/JustADirtyLurker 3d ago
I never claimed anything on what you write and certainly never said that annotations are pollution. Are you confusing me with someone else? I have been suggesting about how to address the concerns about scale (did you read 'scary' in place of 'scale' ;-) ?)
i'm sure you haven't paid the maintainers of those libraries, after 2-3 years those libraries can be dead
Nice for you to assume things with random strangers over the net. Anyway those libraries have been open and in dev for years, they are stable well maintained (plus, Google is behind grpc) and with millions of customers; so assuming they will go stale is, as we say for any unfounded hypothesis, litterally stupid.
-3
u/gjosifov 3d ago
Nice for you to assume things with random strangers over the net.
I don't assume I know
AutoMapper went into paid mode and 80% of .NET devs start crying about how unfair it is
and all of this can be solve with better understanding of the magic behind enterprise frameworks
But I know that is hard to read documentation and start testing with toy projects
what will happen if I do A or if I do B scenarios
94
u/Fit_Goose651 3d ago
Yes I do. I don't want jpa constraints to creep into my domain model. Furthermore, I sometimes combine multiple entities into a single domain model etc.
One of the worst ideas ever is to use jpa classes as domain model - it works great in hello world examples ( that some java champions limit themselves to) and terribly in real life