领域驱动设计——领域模型与 Hibernate 实体

Domain Driven Design - Domain model vs Hibernate Entity

Hibernate Entity 是否与域模型相同?

请参阅以下示例。

方法 1 - 域模型和实体相同 class。 领域模型"is-an"实体

@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
}

方法 2 - 域和实体是不同的函数。 领域模型"has-an"实体

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

    // Getters and setters to set the agentEntity attributes

    // Business logic
}

从以上2种方式来看,哪种是正确的DDD实现方式?我相信方法 2 是正确的方法,因为您实质上是在控制对敏感对象的访问,并且封闭对象(域模型)具有域模型上的所有业务 logic/operations。但我的工作场所同事认为它们本质上是相同的。根据他们的说法,Hibernate Entity 的目的是表示给定系统中的领域模型。将实体建模为域模型实际上使设计更简单。这是因为存储库采用实体来执行 CRUD 操作。所以如果model"has-an"实体,那么repository必须依赖注入到domain model中来保存实体。这会使设计变得不必要的复杂。

由于您在本例中提到了一项技术 Hibernate,这意味着您正在谈论一种实现。领域驱动设计是关于抽象的,例如。 模型,它是实现

模型可以用不同的方式实现。在您的示例中,您展示了两个不同的实现,它们代表相同的 Model.

这篇article讲的是你面临的问题。

您询问 领域模型 是否与 Hibernate 实体 相同。答案是

Hibernate Entity 是一种技术特定的东西,在这种情况下,它是一个对象,是 ORM 框架的一部分。 DDD 定义的 Hibernate EntityDDD Entity 是不同的东西,因为 DDD Entity 是一个抽象的东西, if 定义了一个想法(一种模式)并给出了这个想法是什么以及它代表什么的指导方针。 Hibernate Entity 是一个 Java 对象,它被实例化、跟踪、持久化、丢弃 和垃圾收集。

人们只是对不同的事物使用相同的术语,这会导致混淆(不能责怪他们,命名事物是软件中的两个难题之一)。

您使用 Hibernaty Entities 或任何其他类型的特定技术,例如 Entity Framework Entity(这是同一件事, OO 程序中的对象) 来实现 领域模型。相同的 域模型 可以使用不同的技术以不同的语言实现。这些实现将根据技术提供的内容而有所不同。

例如,如果您正在编写带有 MongoDB 的 NodeJs 后端,并且您想使用 ORM 来实现 域模型 ,您将无法使用Active Record pattern(可能是 Mongoose),因为只有这些是人们实现的(至少我找不到任何其他不是 Active Record 的框架,如果你找到任何请告诉我)。以这种方式实施 DDD 可能非常棘手(而且真的很糟糕)。

DDD book 中,埃里克·埃文斯 (Eric Evans) 谈到了技术如何帮助您实施 模型 或如何与您抗衡。当它与您作对或没有提供良好的机制时,您只能解决这个问题。

有时 ORM 有要求,您不想将这些东西暴露给您的其他代码,因此您可以使用 方法 2 中的包装器。其中一些包括 public get set 方法,public 构造函数等。它们中的大多数使用反射并且可以拥有私有的东西但是仍然存在许多问题,例如拥有一个没有参数的私有构造函数来满足框架并且您的代码因许多与您的模型无关但因为您的框架需要它们而存在的东西变得混乱(YUCK!)。这也可能导致错误。使用默认构造函数而不是使用带有参数或静态工厂方法的漂亮构造函数更容易犯错误。这个包装器可以代表一个更纯粹的领域模型,而没有框架携带的必要的邪恶,因此您可以使用它们。

在一个项目中,这变得如此丑陋,我们决定在 Repositories 中使用原始 SQL,这样我们就不必处​​理框架。实施很好,很纯粹,我们做得更快。有些人认为框架可以加快速度,大多数情况下这是真的,但是当框架与你作对并且代码有错误时,调试就没有乐趣了,所以写一个原始的 SQL 可能是一件轻而易举的事。在这种情况下,通过使用聚合遵循 DDD 的指导方针,我们的模型很好地解耦了,并且我们没有复杂的查询会使开发变慢。

Are Hibernate Entity is same as the domain models?

不是,不是。实际上,它们之间的界限可能非常模糊。

域驱动设计的一个主张是您可以将持久性问题与域模型分开。域模型在内存中保存一些业务当前状态的表示,以及管理该业务状态如何随时间变化的域规则。

存储库充当一种边界,在应用程序中认为域实体都存储在某处的本地内存中的部分与了解数据的非易失性存储的代码部分之间。

换句话说,存储库(在某种意义上)是两个功能;一个知道如何从 "aggregate" 中获取数据并存储,另一个知道如何从存储中读取数据并从中构建聚合。

ORM 是一种将数据从外部关系数据库获取到本地内存的方法。

所以你的负载故事可能看起来像

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

商店可能看起来像

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

在实践中,这是一种痛苦。 ORM 表示通常需要担心诸如代理键、跟踪哪些数据元素是脏的以便优化写入等事情。

所以你经常会看到的是域逻辑最终被写入 ORM 实体,你抛出一堆注释来明确哪些位存在,因为它们是休眠所需要的。

如果你看看隐藏在底部的DDD Cargo shipping example, you'll see that they took this second approach, where the aggregate has a little bit of hibernate support

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

你的同事是对的:这些在最重要的方面是等同的。域模型取决于您的休眠实体。

两者都不符合埃文斯在他的书中描述的内容。

它们看起来都像很多团队在实践中所做的那样。据我所知,将域逻辑直接放入休眠实体是常见的方法。

如果您真的将两者分开,那么您的存储库将类似于

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)
}

如果仔细观察,您会发现域模型独立于休眠模型,但 存储库 依赖于两者。如果你需要改变你的持久化策略,域模型是不变的。

额外的分离度值得麻烦吗?这取决于。用于描述域模型的面向对象模式与 无状态 执行环境之间存在强烈的认知失调。