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

Vin*_*ino 13 entity domain-driven-design hibernate

Hibernate 实体是否与域模型相同?

请参阅以下示例。

方法 1 - 域模型和实体是同一个类。域模型“是一个”实体

@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
}
Run Code Online (Sandbox Code Playgroud)

方法 2 - 域和实体是不同的功能。域模型“有一个”实体

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

    // Getters and setters to set the agentEntity attributes

    // Business logic
}
Run Code Online (Sandbox Code Playgroud)

从以上2种方法中,哪一种是实现DDD的正确方法?我相信方法 2 是正确的方法,因为您实际上是在控制对敏感对象的访问,并且封闭对象(域模型)具有域模型上的所有业务逻辑/操作。但我的同事们认为它们本质上是一样的。根据他们的说法,Hibernate Entity 的目的是表示给定系统中的域模型。将实体建模为域模型实际上使设计更简单。这是因为存储库需要一个实体来执行 CRUD 操作。因此,如果模型“具有”实体,那么存储库必须依赖注入到域模型中以保存实体。这将使设计不必要地复杂化。

exp*_*ble 16

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

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

这篇文章讨论了您面临的问题。

您询问Domain Model是否与Hibernate Entity相同。答案是否定的

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

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

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

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

DDD 一书中, Eric Evans 谈到了技术如何帮助您实现模型,或者如何与您抗衡。当它与您作斗争或没有提供良好的机制时,您就知道如何解决这个问题。

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

在一个项目中,这变得非常丑陋,以至于我们决定在Repositories 中使用原始 SQL,这样我们就不必处​​理框架的所有内容。实现很好,很纯粹,而且我们做得更快。有些人认为框架可以加快速度,这在大多数情况下是正确的,但是当框架与您抗争并且代码有缺陷时,调试就不那么有趣了,因此编写原始 SQL 可能是轻而易举的事。在这种情况下,通过使用聚合遵循 DDD 的准则,我们的模型很好地解耦,并且我们没有复杂的查询,这会使开发变慢。


Voi*_*son 16

Hibernate 实体是否与域模型相同?

不是真的,不是。在实践中,它们之间的界限可能非常模糊。

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

存储库充当一种边界,介于您的应用程序部分之间,这些部分认为域实体都存储在本地内存中的某处,而代码部分则知道数据的非易失性存储。

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

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
Run Code Online (Sandbox Code Playgroud)

商店可能看起来像

Copy data from the aggregate into a hibernate entity
Save the hibernate entity.
Run Code Online (Sandbox Code Playgroud)

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

因此,您经常会看到,域逻辑最终会被写入 ORM 实体,并且您会抛出一堆注释以明确哪些位是存在的,因为它们是 hibernate 所必需的。

如果您查看DDD Cargo 运输示例,您会发现他们采用了第二种方法,其中聚合在底部隐藏了一点休眠支持

域和实体是不同的功能。域模型“有一个”实体

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

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

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

如果您真的将两者分开,那么您的存储库看起来像

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)
}
Run Code Online (Sandbox Code Playgroud)

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

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