您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

如何在DDD中建立领域模型

时间:2023-02-20 16:03:22  来源:51CTO  作者:Thoughtworks洞见

作者 | TWInsights

在前文《​​当我们谈论DDD时我们在谈论什么​​》中我们讨论了DDD的战略设计和战术设计。在本文中我们将继续探讨领域模型。

用领域模型表达领域概念

在实际项目中,模型设计者往往过早陷入具体构造块类型的识别,比如实体、聚合、领域服务,而忽略了领域模型表达领域概念的目的。我们应该基于领域概念设计领域模型,然后再采用合适的模式降低领域模型的复杂度,进一步增加领域模型的表达能力。

领域模型的作用,一方面是关联代码实现,一方面是关联通用语言。我们对于模型和实现的关联轻车熟路,但是对于语言和模型关联往往有待提升。在沟通中刻意使用通用语言可以帮助我们验证模型的合理性。

我们以一个题目为例,方便后续讨论。

活动平台提供用户参与活动得到奖品的功能,吸引用户及潜在用户参与,以达到拉新、促活、引流的目的。

运营人员可以创建和修改活动,活动的配置内容包括活动名称、活动介绍、活动开放的开始时间和结束时间、参与资格、权益。

用户可以看到活动列表,在活动开放的时间段内进入活动页面看到活动介绍。用户在活动页面领取权益,经判断符合资格的用户就会获得一份奖品。权益可能是信用卡积分,也可能是优惠券。

参与资格可能是:一天内注册的用户、VIP用户、当月生日的用户等。客户希望系统可以方便扩展支持灵活的资格类型,以支持多样的活动形式。

对于一个活动,一个用户只能参加一次。

建立模型

第一步是根据需求分析模型。

我们可以找到以下概念:活动、参与资格、权益。其中参与资格是扩展点。

对于需求「一个用户只能参加一次活动的」,需要记录用户是否参与过活动,所以需要「活动参与记录」的概念。

 

 

参与活动的结果可能有2种:符合参与资格则返回权益,不符合则返回「不符合」。所以我们用了Optional<权益>类型。

我们到这里只识别了各种名词,需要走查用例,寻找缺失的概念:

  • 用例1,运营人员可以创建并修改活动
  • 用例2,用户可以参与活动并获得权益

对于用例1,创建和修改活动,目前模型已经满足了需求。

对于用例2,这里有一个模型之外的规则:「一个用户只能参加一次活动」。这是所有活动都需要遵守的规则,我们将其称为「活动通用规则」。

虽然只是一个很简单的逻辑,但是提取「活动通用规则」这个概念非常有用。如果没有这个概念,那么每次去描述这个概念,只能用「一个用户只能参加一次活动的规则」去表示,非常繁琐;也让概念没有安身之地,容易被随便放到万能的Service中。

我们将其加入领域模型。

 

PS:这里故意省略了参与资格的实现。

 

我们没有把「活动通用规则」放到活动概念里,一部分原因是这个判断逻辑不需要具体活动的信息。

使用通用语言验证模型

有了领域模型,就有了通用语言。使用通用语言重新描述需求,并尽量在沟通中使用通用语言。

运营人员可以创建和修改活动,活动的配置内容包括活动名称、活动介绍、活动开放的开始时间和结束时间时间段、参与资格、权益。

用户可以看到活动列表,在活动开放的时间段内进入活动页面看到活动介绍。用户在活动页面领取权益,经判断符合资格参与资格的用户就会获得一份奖品权益。权益可能是信用卡积分,也可能是优惠券。

参与资格可能是:一天内注册的用户、VIP用户、当月生日的用户等。客户希望系统可以方便扩展支持灵活的资格类型,以支持多样的活动形式。

同时有「活动通用规则」:对于一个活动,一个用户只能参加一次。

这里去掉了「开始时间」、「资格」、「奖品」等模糊不清晰的描述。使用基于领域模型的语言,让需求描述清晰没有歧义。

到目前为止,主要的领域模型都已经分析出来。所有的模型都对应明确的领域概念,不多也不少。

识别构造块类型

在分析了领域模型后,我们再来分析构造块类型。

我们通过是否有状态来做区分。

首先识别有状态的对象:活动、各种参与资格、权益、活动参与记录、用户。一般有状态的对象都是事物,对应的构造块类型也就是实体或者值对象。

其次判断其状态是否会改变:

  • 活动会被修改,所以状态会被改变;
  • 参与资格会被修改,但是参与资格从属于活动,修改后可以直接使用新的对象替换旧的,所以可以设计成状态不变;
  • 权益和参与资格一样,也可以设计成状态不变;
  • 活动参与记录,状态可能发生变化;
  • 用户在这个模型中只是临时存在,状态不会变化。

状态会改变的是实体,包括活动和活动参与记录;状态不变的就是值对象,包括参与资格、权益和用户。

最后剩下的就是无状态的对象:活动通用规则。对应的构造块类型是领域服务。

这里的无状态对象大都可以转化成有状态的对象,例如活动通用规则,可以将方法参数的Optional<活动参与记录>变成成员变量。只是这里我们选择了无状态的设计方法。

由于领域服务没有状态,所以可以在应用启动时就创建出来,也可以在使用时才创建。

 

 

经过分析,我们的领域模型都有了类型。

 

 

设计聚合

首先识别生命周期长的领域对象:在一个操作中被创建出来,操作结束后仍会被其他操作使用的对象。活动、参与资格、权益和活动参与记录都是生命周期长的对象。

其他有状态的对象都是临时对象:在一个操作中被创建出来,操作结束后就不会再被使用。模型中的用户,在一次操作中从其他服务获取,使用后即被丢弃。

这里我们总结下各构造块类型的特点:

 

实体

值对象

领域服务

是否有状态

有且状态可变

有且状态不可变

生命周期

长或者短

长短均可

在生命周期的长的对象中,我们要设计聚合。聚合作为操作单元,主要解决以下几个问题:

  • 整个模型往往庞大复杂,为了降低知识负载,需要将其分解成多个小且简单的模型,划分清晰的边界
  • 部分模型对象之间存在一致性规则,例如需要被一起删除,所以需要放在一个操作中
  • 多个用户可能会并发操作模型,为了避免相互干扰,需要让操作单元尽可能小
  • 对于操作单元,需要将其频繁加载到内存中,如果单元过大,往往不能满足性能要求

根据对业务的了解,活动及参与资格、权益都是一起被创建和修改,可以放在一个聚合里;活动和活动参与记录之间没有一致性规则,可以分开;因为活动参与记录数量会很多,如果和活动在一个聚合中,会降低性能。

所以我们将活动、参与资格、权益设计成一个聚合,而活动参与记录作为一个单独的聚合。而活动和活动参与记录分别作为这两个聚合的聚合根。对应的,聚合都会配备其专属的Repository。

 

 

同时加上遍历方向箭头。由于活动是聚合根,从活动可以遍历到聚合内部的参与资格和权益。另外查询活动参与记录,可以通过其Repository,所以没有活动到活动参与记录的箭头。

由于我们将活动和活动参与记录之间划分成不同聚合,那他们之间的关联将使用聚合的ID来关联,而不是聚合本身。

PS:如果使用了关联对象,遍历方向也可以是从活动到活动参与记录。

如何使用领域模型

领域模型已经建立完毕,我们来看如何使用领域模型以满足用例。

运营人员创建活动基本信息及其关联的参与资格和权益。领域模型的客户(一般来说是应用服务),使用运营人员输入的参数构造出活动对象,再利用Repository将其保存。

运营人员修改活动。应用服务利用Repository获取需要修改的活动,再根据运营人员提供的参数修改活动,最后利用Repository保存活动对象。

用户参与活动。应用服务:

  1. 使用活动通用规则判断用户是否可以参加。由于活动通用规则需要用到活动参与记录,因此应用服务会使用Repository获取活动参与记录;
  2. 如果可以参加,则执行活动的参与活动方法获得结果。这需要利用Repository获取用户参与的活动,并构造用户对象(可能需要调用用户服务获取用户信息,但是领域层并不关心这些逻辑);
  3. 如果结果是获得权益,则创建活动参与记录,并利用Repository保存。

考虑到并发情况,应用服务可以在第1步前加锁,并在第3步后释放锁。

再次思考

(1) 配置和参与活动可否是两个模型?

在实现运营人员配置活动的用例过程中,我们会发现可能找到了一个隐藏的领域概念,将输入的参数转换成领域模型的逻辑有些枯燥和复杂,同样将领域模型和数据库的数据模型之间转换也如此。输入参数和数据模型都是只是扁平的数据数据,没有继承结构。如果使用另外一种面向数据的模型,也许这些用例实现起来会简单得多。

每个模型都是为了解决某个问题。这里运营人员配置和用户参与活动是不同的问题,如果用一个模型来解决这两个问题,可能会有些吃力。那么干脆设计成两个模型,使用限界上下文的概念将这两个模型限定在各自的上下文中,也许更加合理。两个模型可以共享同一份数据库数据,并加上一段(非领域层的)逻辑用于模型之间的转换。

这实际上是一种配置-使用模式。在配置阶段,注重配置类型和参数、审批等;在使用阶段,注重逻辑计算和性能。

(2) 活动参与记录是否可以建模成领域事件?

活动参与记录实际上是不可变的,可以将其设计为领域事件。

(3) 用户参与活动的用例里,逻辑复杂,有泄漏领域概念的嫌疑?

如果发现应用服务里逻辑变得复杂,可能意味着我们找到了一个隐藏的领域概念。我们可以定义一个「用户参与活动逻辑」的概念:如果用户通过了活动通用规则的判断,则可以参与活动。将其加入模型和通用语言中,在沟通中验证此概念是否合理。

总结

很多项目虽然也使用了以领域模型为中心的架构,但是设计者仍然是数据模型/贫血领域模型的思考方式,把大量领域逻辑放置在了万能的Service中,让领域概念隐藏在了冗长的过程代码中,无法享受到DDD带来的收益。

最后总结下本文想要强调的要点:

  • 领域模型和领域概念一一对应
  • 领域模型和实现关联,也和通用语言关联。刻意使用通用语言沟通以验证模型是否合理
  • 演示了一种设计领域模型的步骤
  • 构造块类型不是最重要的,领域模型本身更加重要
  • 更多的使用可以表达业务含义的值对象和临时值对象
  • 聚合是一种设计,需要方法权衡
  • 使用Repository、Factory获取和创建领域模型是应用层的职责,领域层应该关注在表达领域概念


Tags:DDD   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  Search: DDD  点击:(12)  评论:(0)  加入收藏
DDD死党:单引擎查询利器
基于索引的单表查询,是 MySQL 正确打开方式!基于 QueryObject 的声明式查询,是简单查询的正确使用方式!1、应用场景单表查询在业务开发中占比最大,是所有 CRUD Boy 的入门必备,所...【详细内容】
2023-12-19  Search: DDD  点击:(126)  评论:(0)  加入收藏
DDD死党:内存Join——将复用和扩展用到极致
1. 为什么"内存Join"是个无法绕过的话题首先,我们先简单解释下,什么是“内存Join”。相信大家对关系数据库的 join 语句肯定不陌生,其作用就是通过关联关系从多个表中查询数据,...【详细内容】
2023-12-14  Search: DDD  点击:(213)  评论:(0)  加入收藏
我们聊聊DDD、SOA、微服务和微内核
DDD、SOA、微服务和微内核,看到经常有人把这几个概念拿出来一起讲。事实上,DDD和其他三个不是一个维度的东西。DDD其实特别好理解,DDD就是领域来驱动设计嘛,是一种设计思想。很...【详细内容】
2023-12-08  Search: DDD  点击:(233)  评论:(0)  加入收藏
DDD架构下的防御式编程:5大关卡共同保障业务数据的有效性
一般情况下,在流程达到存储引擎前,所有的验证规则必须全部通过,尽量不要使用存储引擎作为兜底方案。但有一种情况极为特殊,也就只有存储引擎能够优雅的完成,那就是唯一键保护。1....【详细内容】
2023-12-03  Search: DDD  点击:(142)  评论:(0)  加入收藏
DDD四层微服务架构
一、微服务搭建思路大家看到的这张架构图并不是空穴来潮,它是通过不断演变出来的,我们要从DDD四层架构、微服务架构两个维度去融合理解。这里的DDD四层架构适用于单个服务的工...【详细内容】
2023-11-24  Search: DDD  点击:(216)  评论:(0)  加入收藏
DDD 必备架构--六边形架构
架构是研究“分”和“合”的艺术,通过“分离关注点”将系统拆分为多个部分,然后在“原则和规则”的约束下对组件进行装配,形成高内聚的构件;再根据需求对多个构件进行关联,形成低...【详细内容】
2023-11-09  Search: DDD  点击:(375)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合,你觉得呢?
“数据密集型系统”越来越多的应用程序有着各种严格而广泛的要求,单个工具不足以满足所有的数据处理和存储需求。取而代之的是,总体工作被拆分成一系列能被单个工具高效完成的...【详细内容】
2023-11-08  Search: DDD  点击:(290)  评论:(0)  加入收藏
DDD与微服务集成的第一战役:客户端重试&服务端幂等
当一个接口从简单的内部调用升级为远程方法调用(RPC)会面临很多问题,比如: 本地事务失效。在内部调用时,多个方法通常在同一事务中执行,可以使用本地数据库事务来确保数据的一致性...【详细内容】
2023-10-30  Search: DDD  点击:(381)  评论:(0)  加入收藏
去哪儿网架构演进之路:微服务的尽头原来是DDD……
一、架构设计理念与技术1.架构演变路径图片 单体(又称巨石系统):所有业务融合于一体。在项目早期,公司一般会选择单体以降低运营等各方面成本。 服务化:随着业务飞速发展和流量增...【详细内容】
2023-10-11  Search: DDD  点击:(266)  评论:(0)  加入收藏
▌简易百科推荐
Netflix 是如何管理 2.38 亿会员的
作者 | Surabhi Diwan译者 | 明知山策划 | TinaNetflix 高级软件工程师 Surabhi Diwan 在 2023 年旧金山 QCon 大会上发表了题为管理 Netflix 的 2.38 亿会员 的演讲。她在...【详细内容】
2024-04-08    InfoQ  Tags:Netflix   点击:(0)  评论:(0)  加入收藏
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(6)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
站内最新
站内热门
站内头条