域驱动设计-域模型与休眠实体

时间:2019-04-19 03:24:38

标签: hibernate entity domain-driven-design

休眠实体与域模型相同吗?

请参见以下示例。

方法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
}

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

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

    // Getters and setters to set the agentEntity attributes

    // Business logic
}

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

2 个答案:

答案 0 :(得分:0)

由于您在本例中提到的是Hibernate技术,因此这意味着您在谈论实现。域驱动设计既涉及抽象,例如模型,它是实施

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

article谈论您面临的问题。

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

休眠实体是一项技术特定的东西,在这种情况下,它是一个对象,属于ORM框架。 DDD定义的休眠实体 DDD实体是不同的东西,因为 DDD实体是抽象的东西,如果定义了一个想法(模式)并提供有关此想法及其含义的指导。 休眠实体是被实例化,跟踪,持久化,丢弃的Java对象 和垃圾收集。

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

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

例如,如果您要使用MongoDB编写NodeJs后端,并且想使用ORM来实现 Domain Model ,那么您将不得不使用Active Record pattern(可能是Mongoose)因为这些是人们唯一实现的框架(至少我找不到非Active Record的其他框架,如果有的话,请让我知道)。以这种方式实现DDD可能非常棘手(而且确实很糟糕)。

DDD book中,埃里克·埃文斯(Eric Evans)讨论了技术如何帮助您实现模型或如何与您抗衡。当它与您抗争或没有提供良好的机制时,您将如何解决此问题。

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

在一个项目中,它是如此丑陋,以至于我们决定在存储库中使用原始SQL,因此我们不必处理框架的所有内容。该实现很好,纯净,我们可以更快地完成它。有人认为框架可以加快速度,并且在大多数情况下都是如此,但是当框架与您竞争并且代码有错误时,调试就不那么有趣了,因此编写原始SQL可能很容易。在这种情况下,遵循使用聚合的DDD准则,我们的模型很好地解耦了,并且没有复杂的查询会使开发变慢。

答案 1 :(得分:0)

  

休眠实体是否与域模型相同?

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

域驱动设计的一项主张是,您可以将持久性关注点与域模型分开。域模型将某些业务当前状态的内存表示形式保存在其中,并且控制该业务状态随时间变化的域规则。

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

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

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示例,您会发现他们采用了第二种方法,即聚合的底部隐藏了little bit of hibernate support

  

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

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

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

他们两个看上去都像很多团队在实践中做了 一样。据我所知,将域逻辑直接放入休眠实体是常见的方法。

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

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

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

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