Search code examples
hibernateentitydomain-driven-design

Domain Driven Design - Domain model vs Hibernate Entity


Are Hibernate Entities the 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.


Solution

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