DDD领域驱动设计内容分享(四十五):DDD领域建模

本文探讨了领域驱动设计(DDD)的基本概念,介绍了事件风暴和四色建模法用于领域发现的方法,涵盖了CQRS与EventSourcing在架构设计中的应用,以及整洁架构的实现。强调了DDD在软件开发中的作用,特别是在指导复杂项目设计和微服务架构中的价值。
摘要由CSDN通过智能技术生成

目录

基本概念

领域建模

领域发现

一、事件风暴

描述产品愿景

二、四色建模法

四色建模是哪四色?

分析的五个步骤

四色建模法案例:

领域建模三步曲

架构设计

CQRS与Event Sourcing

下图为一种支持读写分离的演化:

领域驱动架构

整洁架构


基本概念

过去,系统分析和系统设计一直是分离的,就如同我国的“系统分析师”和“系统设计师”两个职称考试一样。这种割裂导致需求分析的结果无法直接用于设计和编程。反之,设计和编程的代码却可能扭曲原始需求,导致客户在运行软件后才发现许多功能并非他们期望的,并且软件无法快速适应需求的变化。

领域驱动设计(DDD)打破了这种分隔,引入了领域模型的概念,将分析、设计和编程统一起来,使得软件能够更加灵活、快速地适应需求变化。

DDD专注于解决复杂性问题,因此其解决思路与传统的CRUD方法完全不同。然而,从程序员的角度来看,掌握DDD并不会感到复杂。从这个角度看,DDD实际上是在研究将包含业务逻辑的if-else语句放在何处的一门学问。

Q:DDD有什么作用?

A:回顾一下我们进行软件设计的初衷是什么?

通过微服务与领域驱动设计,我们的目标是简化设计,降低维护成本,并提高软件交付速度。为实现这一目标,我们需要在实际项目中搭建一个支持微服务、支持领域驱动设计的架构。

Q:DDD适用于什么场景?

A:在Eric Evans所著的《领域驱动设计》一书中,副标题已经明确表明了一切: 软件核心复杂性应对之道。领域驱动设计的真正作用在于对日后维护的项目中,当系统变得越来越复杂时,它才能展现出真正的威力。

对于新项目是否应该采用领域驱动设计(DDD),这取决于项目的特性。即使新项目目前并不复杂,产品仍处于迭代的阶段,领域驱动设计仍然是一种合理的选择。尽管在初期其优势可能不能充分发挥,但考虑到项目可能在未来变得越来越复杂,我们应该从一开始就考虑到代码的增长和潜在的独立子业务。

对于新项目,领域驱动设计更多地用于指导战略层设计,如领域建模。而在战术层面的技术实现方面,可以根据团队成员的熟悉程度选择最合适的方式,以实现持续快速交付和降低维护成本的目标。

图片

领域建模

Q:什么是领域模型?

A:领域模型是为解决特定场景下的问题而形成的一套模型,然后利用这套模型来解决业务问题。 就像我们根据反复经验形成一套模式一样,领域模型也会形成一套模式,其中包括实体、值对象、模块和领域服务。

领域发现

那领域模型是怎么一步一步确定下来的呢?

确定领域模型的过程可以通过以下两种常用的领域发现方法来完成:事件风暴与四色建模法。

一、事件风暴

Event Storming(事件风暴) 是一种轻量级的系统分析方法,基于领域驱动设计(DDD)的概念。它能够帮助我们梳理系统中的各种相关元素,包括核心的聚合。该方法有助于开发人员理清核心业务对象,从某种程度上说,它就是领域对象中的聚合。

描述产品愿景

产品愿景的主要目的是对产品顶层价值进行设计,以确保产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。

产品愿景的参与角色主要包括领域专家、业务需求方、产品经理、项目经理和开发经理。

事件风暴关注点

在事件风暴中,主要关注以下几个方面:

1、事件:某个动作的结果。

2、属性:事件的输入和输出。

3、命令:描述某个动作。

4、实体:执行命令的触发者。

简单理解,即谁(实体)使用什么(输入)做了什么(命令、动作),产生了什么(输出),并影响了什么(事件)。

图片

二、四色建模法

通过还原业务逻辑事件,根据这些事件是否影响公司的运营和发展,确定凭证作为时标型对象,并补充相关描述的建模方法通常可以使用四色建模法。四色建模法一般用于问题分析和建模,通过四种不同颜色的符号来表示事物的不同方面,包括事物的角色、规则、动作和记录。在这个上下文中,凭证作为时标型对象可以通过四色建模法来更全面地描述其在业务逻辑中的角色和影响。

四色建模是哪四色?

这包括:

  • 时标型(Moment-Interval)对象:具有可追溯性的记录运营或管理数据的时刻或时段对象,使用粉红色表示;

  • PPT(Party/Place/Thing)对象:代表参与到流程中的参与方/地点/物,使用绿色表示;

  • 角色(Role)对象:在时标型对象与PPT对象(通常是参与方)之间参与的角色,使用黄色表示;

  • 描述(Description)对象:对PPT对象的一种补充描述,使用蓝色表示。

分析的五个步骤

找到溯源事件:确定业务逻辑中的起始事件或导致其他事件发生的根本事件。

确定时标型对象:针对业务逻辑中涉及的重要时刻或时段,确定并标识出时标型对象,确保能够记录相应的运营或管理数据。

找到周围的PPT对象:识别并记录在上下文中与时标型对象相关的参与方、地点和物,这些信息将成为PPT对象。

找到角色:确认在时标型对象与PPT对象之间参与的角色,并标识为角色对象。

补全描述对象:对PPT对象进行补充描述,使用描述对象,以提供更全面的信息。

以上步骤有助于通过四色建模法全面建模业务逻辑,确保对事件、对象和角色的关系有清晰的了解。

四色建模法案例:

图片

领域建模三步曲

对于不同的人提炼出来的领域模型不可能完全一致,这是因为每个人对业务理解的角度都不同。那么,怎么才能保证建模的正确性?

这听起来是个合理的质疑,但实际上却不是那么有道理。首先我们需要明白建模的目的是什么。如果仅仅是为了描绘问题,那么并没有什么对错之分——仅仅是立场和角度的差别。而如果是为了企业业务系统而进行建模,那么这个问题应该变为:如何确保模型能够支撑企业的运营?

给大家梳理以下几个步骤:

一、统一语言

梳理业务 在进行设计时,梳理业务贯穿整个过程。这需要技术与业务专家利用统一语言,描绘需求或问题本身,不断梳理业务,提炼出核心的领域模型(而非表设计)。

这有利于拉近技术人员与业务人员之间的关系,建立信任,达成统一的业务目标。

二、识别聚合、聚合根 

梳理完业务后,找出实体、值对象,识别各个聚合与聚合根。

如何识别聚合与聚合根?

一个Bounded Context(限界上下文)可能包含多个聚合,每个聚合都有一个根实体,叫做聚合根;

如何进行关联?聚合根到聚合根:通过ID关联聚合根到其内部的实体,直接对象引用;聚合根到值对象,直接对象引用;对于聚合,有以下设计原则:

  • • 聚合是用来封装真正的不变性,而不是简单的将对象组合在一起

  • • 聚合应尽量设计的小,尽可能小的拆分,可以避免重构,重新拆分

  • • 聚合之间的关联通过ID,而不是对象引用

  • • 聚合内强一致性,聚合之间最终一致性

  • • 应用层实现跨聚合的调用,避免跨聚合的领域服务调用和数据表关联。

三、划分限界上下文 

第二步完成后,我们根据不同的场景来划分限界上下文,以便进行微服务拆分。通过这种基于业务理解的拆分方式,我们的系统能够实现高内聚,达到单一职责,拆分出来的每个微服务都是软件变化的一个原因,不会因为某个原因发生的变更去修改每个微服务,“牵一发而动全身”。

图片

架构设计

架构设计作为DDD战术层面的设计,关乎到设计如何落地到项目中。下面介绍跨库关联查询解决方案及几种比较流行的架构设计方案。

方案一:数据冗余

这是最简单而常用的解决方案之一——以空间换时间,将需要关联查询的条件冗余存储在需要查询的数据库中。举个例子,将商品与商品类目拆分到两个独立的服务与数据库中,两者通过类目编码进行关联。如果产品想通过类目名称对商品进行分页查询,我们可以将类目名称冗余存储到商品表中,并给其添加数据库索引即可。

方案二:数据补填

结合Wrapper设计模式,通常在Dao层实现数据聚合。即在本地库完成分页查询后,通过查询条件判断是否需要填充关联数据。若需要,则通过跨服务查询相关联的服务,再对各个服务的数据进行填充组装,最终返回。

以图示为例,若要实现患者预约查询并聚合患者、医生数据,患者预约服务在查询完预约表数据后会补填患者服务和医生服务的数据。

图片

这种方法的缺点就是,当一个完整的数据涉及到N个微服务,就会增加N-1个服务调用,数据全量查询/导出的场景也不好使。

CQRS与Event Sourcing

CQRS(Command Query Responsibility Segregation)指的是命令查询职责分离。在这个模式中,Command服务专门负责写数据,使用关系型数据库以确保ACID事务的一致性;而Query服务专门负责读数据,通常采用NoSQL数据库,实现宽表查询,例如MongoDB、ElasticSearch等。这种设计是一种索引外置的方案,通过将写操作和读操作分离,可以更好地满足系统的需求,提高系统的灵活性和可维护性。

图片

事件溯源(Event Sourcing)简而言之是通过事件来管理领域对象的生命周期。在这种模式下,事件即为领域对象已发生的事实,而且一旦产生的事件不可更改,只能不断追加。一个对象从创建到消亡会经历多个事件,与传统的将对象的最新状态保存到数据库中不同,事件溯源保存的是对象经历的每个事件,按照时间先后有序存放在数据库中。

与传统模式相比,事件溯源更符合事实观,因为它完整地描述了对象整个生命周期中所经历的所有事件。当触发领域对象的某个行为时,该对象会产生一个事件,然后自身响应该事件并更新状态。同时,每个事件都会被持久化,使得要重新获取对象的最新状态时,只需创建一个空的对象,按照事件发生的顺序逐一应用所有相关事件,从而还原对象的最新状态。这个过程即为事件溯源。

另一方面,因为事件表示对象的状态,而事件是不可修改的,这使得数据库中表示对象的数据非常稳定,不会存在DELETE或UPDATE等操作。每个事件都代表一个不可改变的事实,从而确保了领域模型在数据库级别不会出现并发更新同一条数据的问题。

下图为一种支持读写分离的演化:

图片

我们的写服务采用了一个单一的Controller入口,通过URL路径与Body传入参数,请求进入相应的Service方法。通过反射定位到具体的Service方法,逻辑处理后分发到单一的DAO,通过Vobj.xml配置映射到对应的表。这样的设计使新增业务只需编写Service与配置映射,无需新增Controller与DAO,大幅简化了代码。

对于读服务,同样有一个统一的Controller,通过Service分发到不同的DAO层实现。可以选择自定义SQL或利用ES存储宽表来实现查询,实现了读写分离。

领域驱动架构

图片

与上面的单Dao实现类似,建立统一的Controller与通用的仓库与工厂,利用数据库第三范式实现服务内数据补填,利用服务调用实现跨服务数据补填。

整洁架构

整洁架构的核心思想是通过适配器层解耦业务层与技术框架层的代码,从而实现业务代码与技术框架能够独立升级迭代,相互之间不产生影响。众所周知,技术架构一直在不断变化,而项目对技术架构的调整成本通常是相当高昂的。那么,如何降低这种调整成本呢?

图片

如上图所示,中间的Entities与Use Cases属于业务领域层,Entities表示业务领域模型的核心业务,Use Cases表示与用户交互的Service;最外层技术框架层是各种技术实现,与业务无关的一层;那业务与技术怎么进行关联呢?通过中间绿色的接口适配器层实现。适配器层分离了技术实现与业务逻辑。

下图为整洁架构的一种落地方案。

图片

以上介绍了DDD的基本概念、领域建模以及几种主流的架构设计方案。DDD是一个庞大的课题,工程师们对DDD存在着不断的争论,但在争议中也能找到合理的解决方案。并不是每个项目都必须完全实现DDD,但在战略层设计中,它能够指导我们精确梳理业务。

无论如何,如果想成为一名业务架构师,学习DDD领域建模与架构设计是一门必修课。参与到这场思想运动与实践中是非常有必要的,因为它能够为项目提供更清晰的业务视角,引导出更合理、可维护的架构设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值