领域驱动设计——术语篇

2022-07-27 22:42:32 浏览数 (1)

随着微服务架构的普及,领域驱动设计(DDD)又重回软件设计战场。虽然团队内不少项目已经开始尝试,使用DDD指导项目的设计与开发,但还是有不少同学对DDD缺乏基础了解。因此,本文结合书本的定义及个人理解,对DDD中关键概念进行梳理,避免沟通时的歧义。毕竟DDD提倡使用通用语言,业务层面应该使用通用语言,技术层面也应该统一术语。

主要概念

  • 通用语言(Ubiquitous Language)

围绕领域模型建立的一种语言,团队所有成员都使用这种语言把领域的所有活动与软件联系起来。

通用语言是事件风暴中产生的,达成共识的、能够准确描述业务涵义和规则的语言。《领域驱动设计》一书对通用语言的重要性进行了较大篇幅的强调,在整个软件生命周期中,无论是口头交流还是书面表达都应该使用通用语言。

个人认为,对团队新成员进行通用语言培训是有必要的。

  • 实体(Entity)

一种对象,它不是由属性来定义的,而是通过一连串的连续事件和标识定义的。

实体和值对象都是领域知识中的名词,建模时,常常容易混淆。其关键判断依据是,实体是有标识的,要么是全局唯一标识,要么是聚合内部的本地标识。例如,订单ID是全局唯一标识,而订单项ID只需要在订单ID下唯一即可。

  • 值对象(Value Object)

一种描述了某种特性或属性,但没有概念标识的对象。

值对象是由其关键属性决定的,只要关键属性相同,就表示对象相同。值对象应保持其不变性,变更应整体替换。

在实现时,可能表现为在上下文A中为实体,在上下文B中为值对象。例如,“地址”,在订单上下文,它是值对象。但在地址维护子系统,它是实体。修改订单地址,实际上是通过重新选择地址对象,以json字符串等的方式存储于订单实体。地址系统的信息变更,并不会影响到已有订单。

  • 服务(Service)

一种作为接口提供的操作,它在模型中是独立的,没有封装的状态。

服务是无状态的,客户使用它时,不需要关心它的历史。服务在系统运行中通常以单一实例存在,它包含实现了各种业务逻辑的方法。DDD中的服务包括应用层服务和领域层服务。实践中,我们使用Facade命名应用层,它通过封装领域层服务,对外提供接口级别的服务。

  • 仓储(Repository)

一种把存储、检索和搜索行为封装起来的机制。

可以把它看做一个特殊的服务,它专门提供存储相关接口,上层访问与底层实际存储无关。但作为开发人员,仍需了解Repository各接口的实现细节,避免误用导致的性能等问题。

Repository通常可以支持用户定制化的接口(用户指定操作字段等,需要警惕SQL注入问题)和一些常用接口。例如,exec接口,允许用户指定数据表、字段,甚至操作。

  • 固定规则

一种为某些设计元素做出的断言,除了一些特殊的临时情况(例如,方法执行中间,或者尚未提交的数据库事务的中间)意外,它必须一直保持为真。

包括数据一致性规则、必填字段等。例如,订单总价=所有订单项价格 - 各种折扣。

  • 聚合(Aggregate)

聚合是一组相关对象的集合,我们把聚合作为数据修改的单元。

聚合根:聚合中一个代表聚合核心概念的实体。每个聚合有且仅有一个聚合根。

外部只能通过引用聚合根,查询、修改聚合内部数据。聚合内应保持固定规则,即符合数据一致性。

聚合是领域模型的第一层边界,通过组合、拆分聚合,构成微服务单元。

聚合间的调用逻辑应通过应用层服务RPC实现,以确保聚合间的低耦合,便于微服务的重组和拆分。

* 聚合的识别是实际操作中的难点,可以采用自下而上的方法,先将每个实体作为一个聚合,不断组合出合适聚合。

  • 限界上下文(Bounded Context)

限界上下文是规定了领域边界的语义环境,领域内使用通用语言。

它是领域模型的第二层边界,一个限界上下文应包含应用层、领域层、数据层。在软件设计初期,不同限界上下文可能会共享数据库,以降低成本,但仍需要注意分库,或者分表,并避免联合查询,及表间外键的级联更新、删除。

限界上下文可以作为微服务的边界

  • 工厂(Factory)

一种封装机制,把复杂的创建逻辑封装起来,并为客户抽象出所创建的对象的类型。

包括创建工厂和重建工厂(来自存储或网络的数据重建)。

一般对象(包括实体和值对象)的创建有两种方式,简单的对象创建可以由构造函数(Go中没有静态方法,可以用函数)实现;复杂的对象(通常是聚合根)的创建,可以由工厂方法实现。

工厂创建出来的对象必须满足固定规则。固定规则的逻辑根据是否在全生命周期使用,可放置在实体,若仅在创建时校验,可放置在工厂。

实体工厂创建出来的对象仅包含必填字段即可。值对象工厂创建出来的值对象需包含全部字段,因为值对象是不可变的。

  • 领域事件(Domain Event)

领域事件通常是领域知识中的,“当xxx完成,则执行xxx”,当领域事件发生,将进一步触发业务操作。

领域事件是微服务解耦的关键。对非实时一致性场景,事件通过发布、订阅方式实现,达到最终一致性。

领域事件避免了传统RPC的分布式事务难题,减轻了系统实时访问的压力。

* 为了确保可靠性,应根据需要对事件进行持久化

(个人理解和以往的“事件”没有太大区别,重点是识别出领域事件)

  • 贫血模型

领域对象只有属性及其getter/setter方法的纯数据类,业务逻辑通过服务实现。传统的三层架构中的数据类就是这种模式。

  • 充血模型

单个、自身的业务逻辑属于领域对象的行为。

涉及多个领域对象交互的部分属于领域层的服务,其他领域无关的、跨聚合的逻辑属于应用层服务。

参考文献

《领域驱动设计——软件核心复杂性应对之道》

《实现领域驱动设计》

0 人点赞