DDD的技术落地 — 聚合在.NET中的实现

上下文可以从数据库中查询出数据并且跟踪对象状态的改变,然后把对象状态的改变保存到数据库中,因此上下文就是一个天然的仓储的实现

上下文会跟踪多个对象状态的改变,然后在SaveChanges方法中把所有的改变一次性提交到数据库中;

这是一个”要么全部成功,要么全部失败”的操作,因此上下文也是一个天然的工作单元的实现

有一些开发人员会再编写仓储和工作单元的接口以封装上下文的操作。

这样可以把EF Core的操作封装起来,不仅可以让代码不依赖于EF Core,而且今后如果我们需要把EF Core替换为其他持久化机制,代码切换起来也会更容易。

但是本书将直接用上下文作为仓储,而不是定义一个仓储的抽象层,微软也是这样建议的。

因为EF Core是一个很好的仓储和工作单元的实现框架,很难找到另一款可以很好实现DDD的ORM框架。

无论抽象层怎么定义,如果需要把EF Core替换为其他ORM框架,代码就不可能不做任何改变。

我们直接用上下文做仓储,这样可以最大化利用EF Core的特性,从而提供更高性能的仓储实现。

在EF Core中,我们可以不为每个实体类都声明对应的DbSet类型的属性,即使一个实体类没有声明对应的DbSet类型的属性,只要EF Core遇到实体类对象,EF Core仍然会像对待其他实体类对象一样对其进行处理。

由于除了聚合根实体类之外,聚合中其他实体类不应该被开发人员访问到,因此我们可以在上下文中只为聚合根实体类声明DbSet类型的属性。

还有一个问题需要讨论,如果一个微服务中有多个聚合根,那么我们是把每个聚合根实体类放到一个单独的上下文中,还是把所有实体类放到同一个上下文中?

前者的优点是上下文的耦合度更低,聚合根之间的界限划分更清晰,缺点就是开发起来比较麻烦,而且实现跨聚合查询的时候也比较麻烦;

后者的优点是开发难度低,跨聚合查询也简单,缺点就是聚合根在上下文里有一定程度的耦合,我们无法很容易地看到聚合的划分。

本书作者倾向于后者,也就是把同一个微服务中的所有实体类都放到同一个上下文中。

因为虽然聚合之间的关系不紧密,但是它们毕竟属于同一个微服务,它们之间的关系仍然比它们和其他微服务中的实体类关系更紧密,而且我们还会在应用服务中进行跨聚合的组合操作。

如果参与应用服务的组合操作的聚合都属于同一个上下文,我们在进行联合查询的时候可以获得更好的性能,在进行跨聚合的数据修改的保存的时候,也能更容易地实现强一致性的事务。

当然,如果经过良好的微服务拆分设计之后,一个微服务中的部分聚合和其他聚合的关系仍然不紧密,我们也可以把它们放到不同的上下文中。

如果选择了把一个微服务中所有聚合中的实体类都放到同一个上下文中,为了区分聚合根实体类和其他实体类,我们可以定义一个不包含任何成员的标识接口,比如IAggregateRoot,然后要求所有的聚合根实体类都实现这个接口。

由于聚合之间是松耦合关系,它们只通过聚合根的Id进行关联,因此所有跨聚合的数据查询都应该是通过领域服务的协作来完成的,而不应该直接在数据库表之间进行join查询。

当然,对于统计、汇总等报表类的应用,则不需要遵循聚合的规范,我们可以通过执行原生SQL语句进行跨表的查询。

订阅评论
提醒
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
滚动至顶部