为每一个业务微服务或绑定上下文定义一个丰富域模型。html
你的目标是为每一个业务微服务或绑定上下文 (BC) 建立一个内聚域模型。 但请记住,BC 或业务微服务有时可能由共享一个域模型的多个物理服务组成。 域模型必须捕获它所表明的单个绑定上下文或业务微服务的规则、行为、业务语言和约束。数据库
实体表示域对象,主要由其标识、连续性和随时间推移的持久性来定义,而不只仅由构成它们的属性来定义。 正如 Eric Evans 所说,“主要由其标识定义的对象称为实体”。 实体在域模型中很是重要,由于它们是模型的基础。 所以,应对其进行仔细识别和设计。dom
实体的标识能够跨多个微服务或绑定上下文。微服务
同一标识(即,同一 Id
,不过可能不是同一域实体)能够跨多个绑定上下文或微服务建模。不过,这并不意味着具备相同属性和逻辑的相同实体会在多个绑定上下文中实现。 相反,每一个绑定上下文中的实体会将其属性和行为限制为该绑定上下文域中所需的属性和行为。测试
例如,买家实体可能具备某我的的大部分属性,这些属性在配置文件或标识微服务的用户实体中定义,其中包括标识。 可是订购微服务中的买家实体可能具备较少的属性,由于只有某些买家数据与订单流程相关。 每一个微服务的上下文或每一个绑定上下文都会影响其域模型。设计
除了实现数据属性外,域实体还必须实现行为。code
DDD 中的域实体必须实现与实体数据(在内存中访问的对象)相关的域逻辑或行为。 例如,做为订单实体类的一部分,你必须将业务逻辑和操做做为任务(例如添加订单项、数据htm
图 7-8。 实现数据加行为的域实体设计示例对象
固然,实体有时可能不会在实体类中实现任何逻辑。 若是某个聚合内的子实体没有任何特殊逻辑,由于大多数逻辑都在聚合根中定义,则该子实体可能出现这种状况。 若是你有一个复杂的微服务,它在服务类而非域实体中实现了大量逻辑,那么你可能会陷入贫乏域模型中,下一节将对此进行解释。进程
Martin Fowler 在他的博客文章 AnemicDomainModel 中是这样描述贫乏域模型的:
贫乏域模型的基本症状是,乍一看上去像是真实存在的。 它包含一些对象,许多以域空间中的名词命名,这些对象与真实域模型具备的丰富关系和结构相关联。 但当你观察它的行为时,问题来了,你发现这些对象几乎没有任何行为,彻底就是一些 getter 和 setter 而已。
固然,使用贫乏域模型时,将从一组可捕获全部域或业务逻辑的服务对象(传统上称为业务层)中使用这些数据模型。 业务层位于数据模型之上,就像使用数据同样使用数据模型。
贫乏域模型就是一种程序化样式设计。 贫乏实体对象不是真实的对象,由于它们缺少行为(方法)。 它们只保存数据属性,所以它不是一种面向对象的设计。 经过将全部行为放到服务对象(业务层)中,实质上最终会产生面条式代码或事务脚本,于是失去域模型提供的优点。
无论怎样,若是微服务或绑定上下文很是简单(CRUD 服务),仅包含数据属性的实体对象形式的贫乏域模型可能已经足够,不必实现更复杂的 DDD 模式。 在这种状况下,它就是一个持久性模型,由于你特地建立了一个仅包含用于 CRUD 的数据的实体。
这就是为何微服务体系结构特别适用于多体系结构方法(具体取决于每一个绑定上下文)。 例如,在 eShopOnContainers 中,订购微服务实现了 DDD 模式,但目录微服务(一种简单的 CRUD 服务)没有。
有人说贫乏域模型是一种反模式。 这真的取决于你要实现什么。 若是你建立的微服务足够简单(例如,CRUD 服务),则采用贫乏域模型,它不是反模式。 可是,若是须要解决包含大量不断变化的业务规则的微服务域的复杂性,那么贫乏域模型多是该微服务或绑定上下文的反模式。 在这种状况下,将其设计为具备包含数据加行为的实体的丰富模型并实现附加 DDD 模式(聚合、值对象等)可能对这种微服务的长期成功具备极大的好处。
其余资源
DevIQ.Domain Entity \(域实体) https://deviq.com/entity/
Martin Fowler。The Domain Model \(域模型)https://martinfowler.com/eaaCatalog/domainModel.html
Martin Fowler。The Anemic Domain Model \(贫乏域模型)https://martinfowler.com/bliki/AnemicDomainModel.html
正如 Eric Evans 所指出的,“许多对象没有概念标识。 这些对象用于描述某个事物的某些特征。”
实体须要标识,但系统中的许多对象不须要,例如值对象模式。 值对象是一种没有概念标识的对象,用于描述域方面。 这些对象实例化后可表示设计元素,你只会暂时关注它们。 你只关心它们是什么,而不关心它们是谁。 其示例包括数字和字符串,但也能够是更高级别的概念,例如属性组。
一个微服务中的实体在另外一个微服务中可能就不是实体,由于在第二种状况下,绑定上下文可能具备不一样的含义。 例如,电子商务应用程序中的地址可能根本没有标识,由于它可能仅表示我的或公司的客户资料的一组属性。 在这种状况下,地址应归类为值对象。 可是,在电力公司的应用程序中,客户地址对于业务领域可能很重要。 所以,该地址必须具备标识,以便计费系统直接连接到该地址。 在这种状况下,地址应归类为域实体。
有名字和姓氏的人一般是一个实体,由于这我的具备标识,即便其名字和姓氏与另外一组值重合(例如这些名字还指另外一我的)也是如此。
值对象在关系数据库和 ORM(如 EF)中很难管理,而在面向文档的数据库中,它们更易于实现和使用。
EF Core 2.0 包含实体功能,这样能够更易于处理值对象,如咱们稍后详细介绍的同样。
其余资源
Martin Fowler。Value Object pattern \(值对象模式)https://martinfowler.com/bliki/ValueObject.html
Value Object \(值对象) https://deviq.com/value-object/
Value Objects in Test-Driven Development \(测试驱动开发中的值对象)https://leanpub.com/tdd-ebook/read#leanpub-auto-value-objects
Eric Evans。Domain-Driven Design:Tackling Complexity in the Heart of Software.(域驱动设计:软件核心复杂性应对之道) (书;包括值对象的讨论)\https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/
域模型包含不一样数据实体和进程的群集,能够控制功能的重要方面,例如订单履行或库存。 聚合是一种粒度更细的 DDD 单元,用于描述一群或一组可视为内聚单元的实体和行为。
一般基于所需事务来定义聚合。 一个典型的例子就是订单,订单中还包含订单项列表。 订单项一般是一个实体。 但它是订单聚合内的子实体,该聚合还包含订单实体做为其根实体,一般称为聚合根。
识别聚合可能很难。 聚合是一组必须一致的对象,但不能按此挑选一组对象就将它们标记为聚合。 你必须从域概念开始,并考虑在与该概念相关的最多见事务中使用的实体。 那些须要在事务上一致的实体就造成一个聚合。 考虑事务操做多是识别聚合的最佳方式。
聚合由至少一个实体组成:聚合根,也称为根实体或主实体。 此外,它还能够有多个子实体和值对象,全部实体和对象一块儿共同实现所需的行为和事务。
聚合根的目的是确保聚合的一致性;它应该是经过聚合根类中的方法或操做更新聚合的惟一入口点。 只能经过聚合根对聚合内的实体进行更改。 它是聚合的一致性守护者,它会考虑到可能须要在聚合中遵照的全部不变量和一致性规则。 若是单独更改某个子实体或值对象,聚合根没法确保聚合处于有效状态。 这就像一张桌脚松动了的桌子。 保持一致性是聚合根的主要目的。
在图 7-9 中,能够看到一些示例聚合,例如买家聚合,其中包含一个实体(聚合根 Buyer)。订单聚合包含多个实体和一个值对象。