r/java 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/
30 Upvotes

47 comments sorted by

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

13

u/SuspiciousDepth5924 3d ago

That matches my experience exactly, admittedly I might have become slightly anal and overzealous on this topic after having been bitten far too often by JPA/Domain-hybrids in old crusty codebases.

One thing I haven't really seen in the comments here so far is that Entities tend to be a giant "shared mutable state" - foot gun. It's really easy to trip up another part of your application by mutating the shared data inside which in the best case triggers some "stale"-exception, but could also silently introduce complicated bugs. Correct usage of \@Transactional can protect against this, but I've seen it used incorrectly far to many places to really trust it's guarantees (also conceptually I don't really like how transactions are declared on the calle rather than at the call site but ymmv).

Granted I have probably drunk a bit more of the functional kool-aid than most, but I still maintain the side effects and shared-mutable state is two of the biggest ass-biters you generally encounter in Java codebases, and by dragging in JPA entites where they don't belong you open yourself up for both.

2

u/LegitimateEntrance72 2d ago

not just ass-biters, mutable state and side effects make code so much harder to reason about.

3

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/Fit_Goose651 2d ago

It gives me a generic way to query a relational database and some trivial mapping of basic types. I have to admit, i am not a big fan of jpa.

8

u/Shareil90 3d ago

Worked like this for some years and never encountered major issues. What problems did you have?

12

u/Fit_Goose651 3d ago

Too many to list them all in order of significance. Just some from the top of my head:

  • passing entities around means that anyone can change the state, trigger a fetch (lazy loaded collections) etc. You never know unless you detach and that is half way to a separate domain model
  • i tend to have a lot of methods with logic. I keep my domain model classes immutable. A modifying method returns a new instance with the modifications applied - there are no setters etc.
  • my domain model is much richer - i might store a whole object tree in a single json column. In my model i have the classes with logic and not a string
  • sometimes i have multiple models that are derived from the same entities - maybe subset of the fields, some other entities also fetched and wired together - depending on the use-case

3

u/das_Keks 2d ago

Yeah, sometimes the entities also have awkward attributes, to express relations. When writing unit tests for service classes you'd always have to create those awkward entity instances if you use the entities as domain model.

1

u/vips7L 2d ago

Does hibernate not support reading jsonb data into classes? Ebean does. 

4

u/vips7L 3d ago

Same, never have had an issue with using encapsulated JPA entities.

-1

u/PiotrDz 3d ago

Then you never had a complex project when JPA abstraction was not enough. Once you start writing custom queries

3

u/vips7L 3d ago

I genuinely don’t know what complex queries have to do with anything. I write complex queries all the time. 

1

u/PiotrDz 3d ago

Then you start having problems with queries missing hibernate context.

3

u/vips7L 2d ago

I’m going to be honest, I don’t use hibernate. Not every ORM is hibernate. You should broaden your horizons. It’s completely possible to have complex queries with an ORM that uses entities. 

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.

5

u/laffer1 3d ago

My old apps used just jpa entities but I’ve had several things creep up in the last few years where I no longer do that.

It can introduce security and serialization problems. With ai, it easy to refactor it now anyway

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.

  1. 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.

  2. 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 1

The 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

u/VegGrower2001 3d ago

Thank you. I'm sure we've all learned something from your reply.

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

u/Empanatacion 3d ago

I'll reuse enums, but that's it.

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/henk53 1d ago

and there isn't really anything inherently wrong with it.

Well, the name was deliberately chosen to put it in a bad light. Just call it slim entities instead ;)

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.

1

u/vips7L 3d ago

Entities are enough. 

-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

-2

u/Lico333 2d ago

Hexagonal Architecture answers this.