13
votes

Are Hibernate Entity is same as the domain models?

See the following example.

Method 1 - Domain model and Entity are same class. Domain model "is-an" entity

@Entity
@Table(name = "agent")
class Agent
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "agent_number", unique = true, nullable = false)
    private String agentNumber;

    @Column(name = "agent_name", nullable = false)
    private String agentName;

    // Busines logic methods
}

Method 2 - Domain and Entity are different functions. Domain model "has-an" entity

class Agent
{
    // Hibernate entity for this domain model
    private AgentEntity agentEntity;

    // Getters and setters to set the agentEntity attributes

    // Business logic
}

From the above 2 methods, which of them are the correct way to implement DDD? I believe method 2 is the right way because you are essentially controlling the access to a sensitive object and the enclosing object (Domain model) has all the business logic/operations on the domain model. But my workplace colleagues suggests that they are essentially the same. And according to them the purpose of Hibernate Entity is to represent the domain model in a given system. Modelling the entity as the domain model actually makes the design simpler. This is because the repository takes an Entity to execute CRUD operations. So if model "has-an" entity, then the repository must be dependency injected into the domain model to save the entity. This will make the design unnecessarily complicated.

2

2 Answers

16
votes

Are Hibernate Entity is same as the domain models?

Not really, no. In practice the line between them can be very blurry.

One of the claims of domain driven design is that you can separate persistence concerns from your domain model. The domain model holds in memory representations of the current state of some business, and the domain rules that govern how that business state changes over time.

The repository acts as a sort of boundary, between the parts of your application that think that domain entities are all stored in local memory somewhere, and the parts of the code that know about non-volatile storage of the data.

In other words, the repository is (in a sense) two functions; one that knows how to get data out of an "aggregate" and store, another that knows how to read data out of a store and build an aggregate from it.

An ORM is one way to get data from an external relational database into local memory.

So your load story might look like

Use an identifier to load data from the database into a hibernate entity
copy the data from the hibernate entity into an aggregate
return the aggregate

And store might look like

Copy data from the aggregate into a hibernate entity
Save the hibernate entity.

In practice, this is kind of a pain. The ORM representation often has to worry about things like surrogate keys, tracking which data elements are dirty so that it can optimize writes, and so on.

So what you will often see instead is that the domain logic ends up being written into the ORM entities, and you throw in a bunch of comments to make it clear which bits are present because they are required by hibernate.

If you look at the DDD Cargo shipping example, you'll see that they took this second approach, where the aggregate has a little bit of hibernate support hidden at the bottom.

Domain and Entity are different functions. Domain model "has-an" entity

Your colleagues are right: these are equivalent in most important aspects. The domain model depends on your hibernate entities.

Neither of them match what Evans described in his book.

Both of them look like what a lot of teams have done in practice. Putting the domain logic directly into the hibernate entity is, as best I can tell, the common approach.

If you were really separating the two, then your repository would look something like

Agent AgentRepository::find(id) {
    AgentEntity e = entityManager.find(id)
    Agent a = domainFactory.create( /* args extracted from e */ )
    return a
}

void AgentRepository::store(Agent a)
    AgentEntity e = entityManager.find(id)
    copy(a, e)
}

// I think this is equivalent
void AgentRepository::store(Agent a)
    AgentEntity e = entityManager.find(id)
    entityManager.detach(e)
    copy(a, e)
    entityManager.merge(e)
}

If you look carefully, you'll see that the domain model is independent of the hibernate model, but the repository depends on both. If you need to change your persistence strategy, the domain model is unchanged.

Is the extra degree of separation worth the hassle? It depends. There is strong cognitive dissonance between the object oriented patterns used to describe domain models, and stateless execution environments.

16
votes

Since you have mentioned a technology in this case Hibernate, that means that you are talking about an implementation. Domain Driven Design is about both the abstract e.g. the Model and it's Implementation.

Models can be implemented in different ways. In your example you have shown two different implementations that represent the same Model.

This article talks about the problem that you are facing.

You asked if the Domain Model is the same as a Hibernate Entity. The answer is NO.

Hibernate Entity is a technology specific thing, in this case it's an object that is part of an ORM framework. Hibernate Entity and DDD Entity as defined by DDD are different things as the DDD Entity is an abstract thing, if defines an idea (a pattern) and gives guidelines of what this idea is and what it represents. Hibernate Entity is a Java object that is instantiated, tracked, persisted, discarded and garbage collected.

People just use the same term for different things and this can lead to confusion (can't blame them, naming things is one of the two hard problems in software).

You use Hibernaty Entities or any other type of technology specific thing like Entity Framework Entity (that is the same thing, an object in a OO program) to implement a Domain Model. The same Domain Model can be implemented in different languages using different technologies. These implementations will vary based on what the technology provides.

For example if you are writing a NodeJs backend with a MongoDB and you want to use an ORM to implement a Domain Model you will be stuck with using an Active Record pattern (probably Mongoose) because these are the only ones that people have implemented (at least I couldn't find any other frameworks that are not Active Record, if you find any please let me know). Implementing DDD in such a way can be very tricky (and can really suck).

In the DDD book Eric Evans talks about how technology can help you implement a Model or can fight you all the way. When it fights you or doesn't provider good mechanisms you just how to work around that.

Sometimes ORMs have requirements and you don't want to expose these things to your other code, so you can use a Wrapper like in your Method 2. Some of them include things like public get set method, public constructors etc. Most of them use reflection and can have private stuff but still there are many issues like having a private constructor without parameter to satisfy the framework and your code get's messy with a lot of stuff that are not related to your model but are there because your framework needs them (YUCK!). This can lead to bugs too. It's easier to make a mistake by having default constructor instead of having nice constructors with parameters or static factory methods. This wrapper can represent a more purer domain model without having the necessary evil that frameworks carry so you can use them.

In one project this got so ugly that we decided to go with raw SQL in Repositories so we don't have to deal with all the stuff of the framework. The implementation was nice, pure and we did it faster. Some people think that a framework speeds things up and it's true most of the time, but when the framework fights you and the code is buggy, debugging is not fun, so writing a raw SQL can be a breeze. In this case Following the guidelines of DDD by using aggregates our model was nicely decoupled and we didn't have complex queries that can make the development slower.