DDD 实战课
欧创新
人保资深架构师
55517 人已学习
新⼈⾸单¥59
登录后,你可以任选2讲全文学习
课程目录
已完结/共 26 讲
开篇词 (1讲)
DDD 实战课
15
15
1.0x
00:00/00:00
登录|注册

14 | 代码模型(下):如何保证领域模型与代码模型的一致性?

思考题
总结
非典型领域模型
典型的领域模型
领域对象与微服务代码对象的映射
领域对象的设计
从领域模型到微服务的设计
领域对象的整理
领域对象映射到代码模型
代码模型
领域模型与微服务设计

该思维导图由 AI 生成,仅供参考

你好,我是欧创新。
[第 12 讲] 中,我们了解了如何用事件风暴来构建领域模型,在构建领域模型的过程中,我们会提取出很多的领域对象,比如聚合、实体、命令和领域事件等。到了 [第 13 讲],我们又根据 DDD 分层架构模型,建立了标准的微服务代码模型,为代码对象定义好了分层和目录结构。
那要想完成微服务的设计和落地,这之后其实还有一步,也就是我们今天的重点——将领域对象映射到微服务代码模型中。那为什么这一步如此重要呢?
DDD 强调先构建领域模型然后设计微服务,以保证领域模型和微服务的一体性,因此我们不能脱离领域模型来谈微服务的设计和落地。但在构建领域模型时,我们往往是站在业务视角的,并且有些领域对象还带着业务语言。我们还需要将领域模型作为微服务设计的输入,对领域对象进行设计和转换,让领域对象与代码对象建立映射关系。
接下来我们围绕今天的重点,详细来讲一讲。

领域对象的整理

完成微服务拆分后,领域模型的边界和领域对象就基本确定了。
我们第一个重要的工作就是,整理事件风暴过程中产生的各个领域对象,比如:聚合、实体、命令和领域事件等内容,将这些领域对象和业务行为记录到下面的表格中。
你可以看到,这张表格里包含了:领域模型、聚合、领域对象和领域类型四个维度。一个领域模型会包含多个聚合,一个聚合包含多个领域对象,每个领域对象都有自己的领域类型。领域类型主要标识领域对象的属性,比如:聚合根、实体、命令和领域事件等类型。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了如何保证领域模型与代码模型的一致性,重点讨论了将领域对象映射到微服务代码模型中的重要性。首先,文章强调了整理领域对象的重要性,包括领域模型、聚合、领域对象和领域类型四个维度。其次,从领域模型到微服务的设计需要进行进一步的设计和分析,包括分析微服务内有哪些服务、服务所在的分层、应用服务由哪些服务组合和编排完成等内容。接着,文章详细介绍了领域对象的设计过程,包括设计实体、找出聚合根、设计值对象、设计领域事件、设计领域服务和设计仓储等步骤。最后,强调了领域对象的设计需要参与的角色有DDD专家、架构师、设计人员和开发经理。通过实际案例和详细步骤,本文深入探讨了如何保证领域模型与代码模型的一致性,对于需要进行微服务设计和落地的技术人员具有一定的参考价值。文章还介绍了领域对象与微服务代码对象的映射关系,以及非典型领域模型的处理方法。整体而言,本文为读者提供了深入的技术讨论和实践指导,对微服务设计和领域模型的关系进行了全面而深入的探讨。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《DDD 实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(72)

  • 最新
  • 精选
  • 伊来温
    我的回复怎么不见了,再发一下。请教下老师,关于领域代码的分层和编排上一直以来我有个一个疑问。假设我有两个领域聚合跟,用户(User)和企业(Corp),对应的领域服务是UserDomianService和CorpDomainService,那我假如需要这个一个接口listCoprUser来获取企业下面的用户列表, 这个接口该放在哪一层做编排呢。1. 如果放在CorpDomainService里面,则会造成对User实体的引用,造成耦合。2.难道上升到app层做编排么?但listCoprUser又像是一个领域服务。3.又或者做成一个新的领域服务CorpUserDomainService吗?那是不是CorpUserDomainService在代码结构上只有一个领域服务,而没有repository, domain层级了呢?

    作者回复: 从你的场景来看,企业跟用户是一对多的关系吧。你可以这样设计,在企业聚合中将用户的相关信息设计为一个包含若干用户属性的用户值对象,然后企业聚合根引用用户值对象,用户值对象的数据来源于用户聚合。 用户聚合包含了全量的用户数据,而在企业聚合中用户值对象只是简单必须的用户数据,通过这种数据冗余的方式,在企业聚合中,就可以通过企业领域服务中一次获取企业和它相关的用户数据清单。

    2020-07-02
    13
    25
  • Jack.Chen
    希望把完整样例代码放出来

    作者回复: 这两天会加一篇完整的代码详解。

    2019-12-30
    6
    16
  • 冯磊
    感觉application这一层完全可以去掉,intrerface直接调domain service就可以的。作者能解释一下application这一层为什么必须存在吗?

    作者回复: 应用层连接用户接口层和领域层,它是很薄的一层,主要职能是协调领域层多个聚合完成服务的组合和编排。 应用层之下是领域层,领域层是由多个业务职责单一的聚合构成,实现核心的领域逻辑。应用层负责协调领域层多个聚合的领域服务或领域对象,面向用例和业务流程完成服务的组合和编排。所以理论上应用层不应该实现领域模型的领域逻辑。这也是应用层为什么会很薄的原因。 应用层之上是用户接口层,在应用层完成领域层服务组合和编排后,应用服务被用户接口层Facade服务封装,完成接口和数据适配后,以粗粒度的服务通过API网关面向前端应用发布。 此外,应用层也是微服务之间服务调用的通道,微服务在应用层可以调用其他微服务的应用服务,完成微服务之间的服务组合和编排。 在应用层主要有应用服务、事件订阅和发布等相关代码逻辑。 其中,应用服务主要负责服务的组合、编排和转发,处理业务用例的执行顺序以及结果的拼装。在应用服务中还可以进行安全认证、权限校验、事务控制、领域事件发布或订阅等。

    2020-06-29
    4
    13
  • suke
    老师 请问依赖倒置是如何体现的?还有所谓的充血模式,最好能有具体的代码说明,不然还是觉得很空洞

    作者回复: 依赖倒置的代码在加餐里面会有详细说明。 一、依赖倒置(DIP)设计:是指面向接口编程,而不是面向实现编程。这样可以避免业务逻辑与实现逻辑的耦合,在实现逻辑出现变化时,降低对业务逻辑的影响。 为了解耦领域逻辑和数据处理逻辑,我们在领域层和基础层之间增加了薄薄的一层,这一层就是仓储。 仓储模式包含仓储接口和仓储实现,仓储接口面向领域层提供基础层数据处理相关的访问接口,仓储实现完成仓储接口对应的数据持久化相关的逻辑处理。一个聚合会有一个仓储,统一由仓储来完成聚合数据的持久化。 领域层业务逻辑面向仓储接口编程,当聚合内的实体数据需要持久化时,只需将领域对象DO对象转换成PO持久化对象,然后传递给仓储接口,通过仓储实现完成DO数据的持久化工作。这样领域层就可以更好的聚焦于聚合的领域逻辑,而不必关心实体数据在基础层到底是如何实现持久化的了。 仓储接口的实现逻辑非常简单,只需要在仓储接口类中,定义仓储实现的基本接口和参数就可以了。 仓储接口代码如下: public interface PersonRepository { void insert(PersonPO personPO); void update(PersonPO personPO); PersonPO findById(String personId); PersonPO findLeaderByPersonId(String personId); } 仓储实现会根据仓储接口的数据处理逻辑要求,调用DAO完成数据查询或数据持久化,如基于聚合根ID的查询,聚合中新增或修改等领域对象数据的持久化操作。假如数据库需要技术升级,我们只需调整仓储实现的数据处理逻辑,适配新的数据库就可以了,这种调整不会影响领域逻辑。 仓储实现代码如下: public class PersonRepositoryImpl implements PersonRepository { @Autowired PersonDao personDao; @Override public void insert(PersonPO personPO) { personDao.save(personPO); } @Override public void update(PersonPO personPO) { personDao.save(personPO); } @Override public PersonPO findById(String personId) { return personDao.findById(personId).orElseThrow(() -> new RuntimeException("未找到用户")); } @Override public PersonPO findLeaderByPersonId(String personId) { return personDao.findLeaderByPersonId(personId); } } 在领域服务中,可以调用仓储接口完成数据持久化操作。由于领域服务只与仓储接口发生调用关系,数据的持久化逻辑在仓储实现中完成。因此在更换数据库时,只要仓储接口不变,领域服务的逻辑就可以一直保持不变。 领域服务如下: public class PersonDomainService { @Autowired PersonRepository personRepository; public void update(Person person) { personRepository.update(personFactory.createPersonPO(person)); } } 这样就保持了领域层领域逻辑的稳定,实现了领域层与基础层的解耦和依赖倒置。 二、充血模型与贫血模型的关键差异: 在充血模型中,业务逻辑都在领域实体对象中实现,实体本身不仅包含了属性,还包含了它的业务行为。DDD领域模型中实体是一个具有业务行为和逻辑的对象。 而在贫血模型中领域对象大多只有setter和getter方法,业务逻辑统一放在业务逻辑层实现,而不是在领域对象中实现。

    2020-06-27
    4
    12
  • ANYI
    1,对于实体采用充血模型,包含自己的属性及行为,例如保持、更新、删除等行为方法,需要持久化,依赖基础层数据库操作,是在实体直接引入,例如mybatis的mapper? 2,对于相对简单的实体操作增删改查这种,需要暴露到接口层;那要一层一层向上封装,实体》领域服务》应用服务》接口服务;这样是不是又显得代码很多余;一个简单的增加修改方法接口,需要很多冗余代码,上层也没有其他逻辑,封装一下调用下层,写一个接口,要写很多层次调用,是否会很臃肿啰嗦,是不是就可以直接接口层封装就省去一些层呢? 3,在服务编排上有没有一些框架什么的?还是都是通过if else的手写?

    作者回复: 1、实体的这些数据库映射是通过mapper来实现的。 2、松散分层架构是可以跨层调用了,实现起来很容易。但是在复杂的情况下,服务不太容易管理,比如,你可能不知道你的方法到底被谁组合和封装了,一旦出现方法变更,你不容易一次找出所有受影响方。而逐层封装的话,你只需要逐层通知到上层就可以了。 3、微服务内的服务编排相对简单,就是业务逻辑的执行顺序而已,个人感觉不需要引入什么工具。

    2019-11-15
    7
    5
  • Jxin
    1.同求代码案例。(基于一个非ddd微服务的demo,分支形式实现微服务内部代码规范,跨服务间代码重组) 2.代码案例这个成本很大,但还是厚颜无耻的提了。毕竟缺少代码这个实体,这个专栏感觉就少点东西。毕竟讲得再抽象精准,可能也没有展示code来得直接明了。 3.我们需要从实悟虚,从虚就实。如果理论能结合code案例,这个专栏的学习成本和实用性将会有质得飞跃。

    作者回复: 谢谢你的建议。后面准备准备,可能需要点时间。

    2019-11-15
    5
  • 日月星辰
    同一个微服务里不同领域之间的调用可以在应用层直接调用吗?

    作者回复: 同一个微服务不同聚合之间为了解耦,不建议聚合之间直接调用。你可以将聚合之间的调用提升到应用层,通过应用服务来实现跨聚合的组合和调用。

    2020-06-02
    4
  • Peter Yu
    老师,aDomainService何以调用bDomainService的方法吗。比如之前有个同学提问:Corp和User属于两个领域,但是Corp中有个查询user的服务,你建议他将此方法放在domainService层,那同步user的数据时,corpDomainService岂不是得调用userDomainService了?

    作者回复: 聚合之间的领域服务是不建议相互调用的,这样聚合之间会产生耦合,不利于未来领域模型演进和聚合的拆分。聚合之间有两种协作模式,一种是领域事件驱动的模式,可以实现聚合之间数据的传输,另外一种是在应用层通过应用服务来组合和协调不同聚合的领域服务,完成跨聚合的调用和操作。

    2020-11-26
    3
  • Geek_deb968
    大部分业务场景其实都是查询的比较多,关于领域模型我现在看到的和理解到的都是实体简单业务操作,我十分希望能看到关于查询在DDD上代码是怎么实现的,比如门店是一个聚合根,门店菜系设计为值对象,那么我根据菜系查询门店是不是在领域模型上很难操作了,感觉领域模型都是在实体也就是满足确定了唯一标识的情况下,才能发挥作用,动态的查询在DDD上是需要怎样实现呢

    作者回复: 复杂的查询一般都不走领域模型,一般这种查询你可以用原来的查询设计方式,或者采用读写分离方式。在DDD领域模型中主要是基于聚合根的id的查询。

    2020-03-28
    3
    3
  • 如何识别出聚合根?

    作者回复: 一个参考是聚合中的关键实体,另外可以根据引用关系来判断,在所有具有引用关系的实体或值对象中,处于根位置的就是聚合根。

    2020-03-21
    3
收起评论
显示
设置
留言
72
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部