领域驱动设计 学习笔记

用户头像
半亩房顶
关注
发布于: 2020 年 07 月 29 日
领域驱动设计 学习笔记

前言



初次接触领域驱动设计,总结了下公司某大佬的分享,摘选部分,记录如下:



业务架构设计演进



事务脚本 + 贫血模型



实现业务逻辑是最后一步,并且业务逻辑代码的开发是过程式的,这种开发方式在行业里面有一个特定的名称-事务脚本(过程式的),它有以下几个特点:



  • 面向数据建模

  • 事务脚本

  • 数据驱动设计

  • 简单业务开发快



模型中只有数据,没有行为,这时候模型只是数据库表在代码中的映射,在软件设计里面有一个专有名词描述这种现象-贫血模型



service + 贫血模型



随着需求越发复杂,我们的代码中出现了很多复用、职责不单一等坏味道,所以我们逐步的需要用到一下几个技能:



  • 服务建模

  • 单一职责原则

  • 服务驱动设计



这套开发流程,我们称它为“service + 贫血模型”,它有如下几点优势:



  • 服务更能清晰的表达业务提供了那些能力,了解一个系统时第一步应该是去看它包含了哪些服务

  • 引入新的一层(Service),可以有效的降低耦合性,提高扩展性,比如提高实时性时,只需要引入消息队列等更优实现方案即可,Service的代码是不需要变更的。



Service的设计有一些原则需要把控



  • 服务方法与业务用例一一对应

  • 业务方法与事务一一对应:也即每一个业务方法均构成了独立的事务边界。

  • 与UI或通信协议无关:Service的定位并不是整个软件系统的门面(Controller,Task才是门面),这意味着Service不应该处理诸如UI交互或者通信协议之类的技术细节。



service + 充血模型



随着需求更加的复杂,业务体量越来越大时,我们需要如此改进:





(1)事务控制不要放在领域模型的对象中实现,可以放在facade中完成。

(2)领域模型对象中只保留该模型驱动的一般方法,对于业务特征明显的特异场景方法调用放在facade中完成。



充血模型和领域驱动设计



而领域驱动设计,也就是DDD,是建立在充血模型之上的,即:



领取驱动设计中的领域模型都是充血模型



要理解领域驱动设计,就一定要知道什么是”领域“:



领域与具体开发技术无关。领域是你的软件系统要解决的实际问题相关的所有东西的集合。 比如跟谁学好课系统,那么如何创建课程、如何决定优惠规则、如何安排作业、如何管理学生、如何分析销售数据等等,这直接与业务相关的所有东西都归属于“领域”。



进而就是领域驱动设计:



领域驱动设计就是说你得先把“领域”中涉及到的数据、流程、商业规则等都弄明白了,然后以面向对象的观点为其建立一个模型(即领域模型,也就是上面的充血模型),再使用合适的软件技术去实现这个模型。 是你为领域所建立的那个模型,决定了你将用什么技术、采用什么架构、基于什么平台来实现这个软件系统。 所以说是领域”驱动“系统设计。技术在这个过程中是“被动的”,是被“选来”实现“领域模型”的,对于项目的成败,技术不是决定性因素,领域模型是否符合事物的本质才是关键。 基于对事物错误的理解而创建的领域模型,将导致开发出来的软件系统没有长久的生命力,随着时间的推移,很快就会被抛弃,因为它不能解决真正的解决现实问题。



在编码实现业务功能时,通常用2种工作流程:

1、自底向上:先设计数据模型,比如关系型数据库的表结构,再实现业务逻辑。我在与不同的程序员结对编程的时候,总会是听到这么一句话:“让我先把数据库表的字段设计出来吧”。这种方式将关注点优先放在了技术性的数据模型上,而不是代表业务的领域模型,是DDD之反。



2、自顶向下:拿到一个业务需求,先与客户方确定好请求数据格式,再实现Controller和ApplicationService,然后实现领域模型(此时的领域模型通常已经被识别出来),最后实现持久化。



一些概念



实体



DDD规定,具有唯一标示属性的对象,叫做实体



值对象



当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。



聚合 和 聚合根



  • Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。聚合是个复合类型,它是一组对象的集合。

  • 聚合根应该保证业务的内聚性,在一个课程模块中,Course实体对象、Lesson实体对象和CutRule值对象共同组成课程聚合,而Course实体对象又是课程聚合的聚合根,任何对课程的写操作都应该透过聚合根进行

  • 聚合根的内容要保证数据一致性(这里的一致性指的不是数据持久化的事务一致性,而是业务数据的一致性,包含业务上的业务校验) 比如订单和订单详情,一个没有订单详情的订单是不完整的

  • 聚合根的实现应该与框架无关:既然DDD讲求业务复杂度和技术复杂度的分离,那么作为业务主要载体的聚合根应该尽量少地引用技术框架级别的设施,最好是POJO。试想一下,如果你的项目哪天需要从Spring迁移到Play,而你可以自信地给老板说,直接将核心Java代码拷贝过去即可,这将是一种多么美妙的体验。又或者说,很多时候技术框架会有“大步”的升级,这种升级会导致框架中API的变化并且不再支持向后兼容,此时如果我们的领域模与框架无关,那么便可做到在框架升级的过程中幸免于难。

  • 聚合根之间的引用通过ID完成:在聚合根边界设计合理的情况下,一次业务用例只会更新一个聚合根,此时你在该聚合根中去引用另外聚合根的整体有什么好处呢?在本文示例中,一个Order下的OrderItem引用了ProductId,而不是整个Product

  • 聚合根内部的所有变更都必须通过聚合根完成:为了保证聚合根的一致性,同时避免聚合根内部逻辑向外泄露,客户方只能将整个聚合根作为统一调用入口。

  • 如果一个事务需要更新多个聚合根,首先思考一下自己的聚合根边界处理是否出了问题,因为在设计合理的情况下通常不会出现一个事务更新多个聚合根的场景。如果这种情况的确是业务所需,那么考虑引入消息机制事件驱动架构,保证一个事务只更新一个聚合根,然后通过消息机制异步更新其他聚合根。

  • 聚合根不应该引用基础设施。

  • 外界不应该持有聚合根内部的数据结构。

  • 尽量使用小聚合。



领域服务



业务逻辑应该优先写在聚合内,当业务逻辑不属于一个聚合时,可以写在领域服务中,比如有些业务逻辑需要协调多个聚合才能完成,这种可以在Service中实现。



以上整个活动在DDD中称为领域建模,在整个过程中我们不需要考虑任何技术相关的东西,都是在分析业务逻辑,并按照DDD的指导思想进行代码实现。



仓库



当Domain层的代码都编写完之后,该仓库登场了,它负责持久化聚合到存储中,也负责在需要的时候从存储中加载数据并重建整个聚合,在这个阶段我们才需要关注技术细节,应该选择什么类型的数据库,表结构应该怎么设计,这个步骤是服务领域建模的。



Repository 和 DAO 有什么区别呢?从技术上讲,Repository和DAO所扮演的角色相似,不过DAO的设计初衷只是对数据库的一层很薄的封装,而Repository是更偏向于领域模型。另外,在所有的领域对象中,只有聚合根才“配得上”拥有Repository,而DAO没有这种约束,你可以一张数据库表一个DAO。



引用



后端开发实践系列——领域驱动设计(DDD)编码实践

架构蓝图--软件架构 "4+1" 视图模型

领域模型、贫血模型、充血模型概念总结

复杂业务代码要怎么写

领域驱动设计(DDD)编码实践



引申学习








欢迎大家关注我的公众号



半亩房顶



发布于: 2020 年 07 月 29 日 阅读数: 121
用户头像

半亩房顶

关注

人生那么长,能写多少bug? 2018.11.16 加入

我希望,自己永远是自己。我希望,远离bug。

评论

发布
暂无评论
领域驱动设计 学习笔记