在复杂领域中设计软件:领域驱动设计 - levelup


软件的核心是处理和解决可用的业务上下文和问题。今天的企业业务领域通常是庞大而复杂的,并且正在增长/变化——以及接受这种增强的软件。领域驱动设计 (DDD) 是一种软件开发方法,其中软件工件与核心业务概念和目标完全一致。
领域驱动设计由Eric Evans在他的《领域驱动设计:解决软件核心的复杂性》一书中提出。它由模式、原则和集合组成,使团队能够专注于业务成功的核心。
“领域驱动设计 (DDD) 是一种通过将实现与核心业务概念的演变模型深度连接来开发复杂需求的软件的方法” — http://dddcommunity.org

建模问题域——战略 DDD
战略 DDD 侧重于高级架构设计决策。它涉及对业务流程进行建模、将域分解为子域以及它们之间的集成模式。无处不在的语言、有界上下文和上下文映射是领域战略图景的关键组成部分。

无处不在的语言——提炼领域知识
与领域专家的持续合作是战略设计的核心,以理解和传达领域见解。领域概念需要在利益相关者(领域专家、开发人员、产品经理、设计师)之间以相同的术语、含义和上下文进行交流,而不会产生歧义。
最终目标是在对话中创建术语/语言的一致性,以创建反映通信语言的模型/代码。例如,如果您需要在订阅期间定义创建用户的方法,请使用方法名称signupForSubscription而不是create方法。
有效的领域建模者是优秀的知识处理器。有关无处不在的语言的发展和挑战,请参阅此处

限界上下文——大而复杂的领域?分而治之
“哪里出了问题,就是太大了”——利奥波德·科尔
一个复杂的领域模型可以分成更集中的领域来管理复杂性并带来设计和执行的灵活性。为了划分(和征服)复杂的域,我们需要创建一个逻辑边界来分解为子域。子域是反映组织业务流程的问题空间的一部分。
在这篇博文中,我们将考虑一个典型的电子商务应用程序示例——

有多个子域(核心、支持或通用)。为了解决这个问题,我们引入了限界上下文Bounded Context ,它是特定领域模型一致适用的子领域适用性的有形边界。

让我们考虑两个有界上下文——付款和运输。在支付上下文中,客户代表信用卡信息、税率、公司编号和账单地址等属性。在运输上下文中,客户数据可以是送货地址、送货时间偏好。这两个上下文的术语(语言)和领域专家可以是不同的。

上图是同一个对象携带不同的上下文

识别域中的限界上下文
领域知识的深度洞察对于识别限界上下文至关重要。业务相关性、自主性、语言边界、数据流以及与其他上下文的关系是识别有界上下文的线索。事件风暴(用于高级概述的研讨会方法)和限界上下文画布(用于设计和文档的工具)有助于探索限界上下文。
上下文映射
有界上下文在实现业务目标方面不是独立的。需要它们之间的集成。上下文映射是对系统的不同有界上下文之间的关系进行识别、分类和可视化描述的过程。下图显示了不同的上下文映射模式。

上图是上下文映射模式(图片来源:https ://github.com/ddd-crew )

战术设计模式——在有界上下文中塑造代码
战术设计模式是在代码级别实现有界上下文的指南。需要精确定义域模型并在有界上下文中应用。

  • 实体:实体是具有唯一标识(数据库中的主键或系统生成的 ID)的对象,该标识随时间持续存在。客户可以是电子商务应用程序中具有名称、电子邮件和地址属性的实体。
  • 值对象:没有身份的对象,纯粹由其属性定义。让我们考虑一个货币价值对象 - 100 AUD。这个值对象是可比较的(可以与 比较90 USD)、不可变的(创建一个新的而不是替换它)和自我验证器(不能有一个25 ABC无效货币的值对象)。
  • 聚合:聚合是定义事务一致性边界的实体和值对象的封装。假设你有一个OrderwithOrderInfo和OrderItems。更改OrderInfo可能会更改订单的状态。同样,订单总量可以随着变化而变化,OrderItems所有这些都有一个严格的事务边界,需要始终保持一致。一起,Order和OrderInfo形成OrderItems一个聚合体。聚合中的确切一个实体是其标识符用于查找的根。根实体的子实体通过来自根的以下指针来引用。
  • 存储库和工厂:工厂用于通过提供实体和值对象来创建新的聚合。存储库提供了访问持久存储的逻辑封装。

分层架构
每个有界上下文都被划分为以下有凝聚力的水平层。

用户界面
该层为用户提供接口。该层负责显示数据和捕获用户命令。

应用层
业务流程在这一层处理。域实体在此处创建并进行更新。

领域模型层
域的数据和行为在域模型中进行概念建模。这里定义了实体、值对象、聚合、工厂和接口来表达领域规则和逻辑。领域模型的设计通常不依赖于其他层、第三方或持久层。然后应用层协调这些决策。领域模型层和应用层分离的细节参考这里

基础设施层
最初保存在域实体中的数据被持久保存在数据库或另一个持久存储中。

代码实现

DDD什么时候适用?
DDD 不仅仅是一个编码配方。它是关于有助于需要理解业务语义(概念和行为)的系统的战略思维方式。然而,这不是一个全有或全无的交易。我们可以根据您的项目实施这些想法(战略或战术方法)。DDD 方法可能不适合以 CRUD 为中心的系统。

DDD 和微服务
微服务是一种架构模式,用于通过松散耦合、可独立部署、小型且可重用的服务(微单元)来设计应用程序。DDD 有助于通过“有界上下文”来保持服务范围较小。单个有界上下文通常映射到一个微服务,但它可以具有一对多的关系。DDD 有助于识别子域和上下文映射,这有助于微服务之间的通信。
Uber 实现了基于 DDD的面向领域的微服务架构 (DOMA) 。您可以在此处找到设计面向 DDD 的微服务的指南。

处理单体
单片应用程序非常适合开始。随着产品的发展,它会变得难以管理,我们需要进入SOA。这种过渡可以通过基于 DDD 的中间模块化方法来促进。

应用 DDD 的挑战
应用 DDD 时的一些常见挑战包括创建通用语言的时间和精力、领域专家一开始的需求、抽象开销、事务管理、领域建模的合理性。