关键词:
第一章DDD对我而言
还可以指引构建正确软件模型的方向。
领域驱动对团队人的要求较高:
- 具备深厚的业务能力(领域专家)
- 具备业务抽象能力;
- 具备技术抽象能力
DDD 领域驱动设计 可以实现目标
- 如果你希望打磨软件匠艺并提高项目的成功率;
- 如果你迫切期望创造软件来帮助企业把业务竞争力提升到新高度;
- 如果你期望实现出来的软件既能正确地对业务需求建模又可以采用最新建的软件架构进行扩张;
设计
设计是不可或缺的,除了优秀设计就是糟糕设计,根本不存在不做设计.
有效设计(Effective Design)
可以满足商业组织希望借助软件超越竞争者的诉求,它可以驱动企业去思考哪些核心业务必须成为其竞争力,
战略设计
强调业务战略上的重点,如何按重要性分配工作,以及如何进行最佳
- 运用限界上下文(Bounded Context)的战略设计模式来分离领域模型;
- 在明确的限界上下文中发展一套领域模型的通用语言;
- 通过子域处理遗留系统中无边界的复杂性,以及如何改进新项目上的成果;
- 通过上下文映射来集成多个限界上下文;
战术设计
战略设计强调的框架,战术设计强调的是细节;
聚合模式(Aggregate):将若干实体和值对象以恰当的大小聚集在一起;领域事件(Domain Events):既可以让你明确的建立模型,也可以把模型内部发生的事情分享给需要知道这一切的系统
第二章 运用限界上下文与通用语言进行战略设计
DDD主要关注的是如何在明确的限界上下文中创建通用语言的模型;
什么是限界上下文?
- 是语义和语境上的边界;
- 边界内每个代表软件模型的组件都有特定的含义并处理特定的事务;
- 限界内的组件有特定的上下文语境和语义;
- 限界上下文可以理解为问题空间的一部分;
- 软件模型逐渐清晰时,限界上下文将会被迅速转到解决方案空间;
- 模型在限界上下文中实现;
- 当限界上下文被当作组织的关键战略举措进行开发时,即被称为核心域。
- 强调内部的严谨性;
问题空间
是在给定项目的约束条件下进行高级战略分析和设计各个步骤的地方。简单直白些,就是为了完成目标的问题与风险;
解决方案空间
是真正实施解决方案的地方,这些解决方案在问题空间讨论中被识别为核心域。
通用语言
在限界上下文中发展了一种语言用于表达其边界内的软件模型,这一语言由在该限界上下文中开发软件模型的每个团队所使用。
- 是软件模型团队日常交流时使用的语言
- 软件模型的源代码就是这种语言的书面表达方式。
必须:严谨、精确、紧凑
核心域(core Domain)
当限界上下文被当作组织的关键战略举措进行开发时,被称为核心域。
- 最重要的软件模型(取得成功的手段)
- 使组织在与其他组织竞争中脱颖而出
- DDD的首要价值主张
- 也是资源需要重点投入的地方 我们必须将核心域限制在最基本的模型元素范围内,否则交付的就不是最优价值的;
限界上下文、团队和源代码仓库
- 一个团队应该在一个限界上下文中工作;
- 每个限界上下文应该拥有一个独立的源代码仓库;
- 一个团队可能工作在多个限界上下文中;
- 多个团队不能在同一个限界上下文上;
- 必须通过接口来调用限界上线文(微服务本身也是这种思想);
大泥球(Big Ball of Mud)
- 系统由多个没有明确边界并纠缠在一起的模型组成
- 各种毫不相干的概念充斥在众多的模型中;
- 各种自相矛盾的元素相互关联;
- 多个团队在其中工作;
领域专家和业务驱动
- 有一个好的业务专家,能理清各业务边界;
- DDD强调不同的概念类型分离到不同的限界上下文中,以此来拥抱变化;
战略设计是必要的根基
限界上下文和通用语言,是战略设计部分基本工具;限界上下文会明确什么是核心;
测试收益:测试会聚焦于一个模型中,这样测试的数量会更少,执行更快。
只有经过“仅限核心”的严格过滤之后保留下来的概念,才能成为拥有限界上下文的团队的通用语言一部分。限界上下文的边界强调其内部的严谨性;
如何确定核心?
领域专家(Domain Expert)和软件开发人员通力合作来确定核心;
领域专家:主要专注于业务,对业务进行规划、抽象;领域专家的心智模型将会成为团队通用语言的坚实基础;
开发人员:将精力花费在编程语言和技术研究中;
DDD:专注业务复杂性而非技术复杂性;有利于业务模型高度复杂的项目开发;
通用语言:是通过协作反馈循环而发展出来的,从中可以促成团队形成共同的心智模型(产品与技术的目标对齐);
发展通用语言
我们应当使用一组具体场景来表达核心域,而不要将核心域局限在名词上。使用DDD赋予的能力:在于可以真正地通过对话了解领域模型如何设计。
推导应用场景
使用被实例化需求的技术(行为驱动开发BDD) 推导出来里面包含的业务逻辑节点,使用假如/当/那么(Given/When/Then)的方法来推导 在业务逻辑推导出来后,单元测试的框架也就出来了;
持续学习改进通用语言
架构
六边形架构:
端口和适配器的“应用-端口-适配器”是“由内向外”的三层, 它将核心业务逻辑(应用层和领域层)和外层的API接口(端口层)以及外部各种具体实现的依赖(适配器层、如各种前端界面、数据库、第三服务、消息机制等)解耦开 通过依赖注入等手段,让架构更具灵活性和可扩展性的同时,也让团队把更多的精力聚焦在核心的应用层上;
三层应用架构:数据-应用-展现
领域模型本身和技术无关;
除了六边形架构,其他可用架构
- 事件驱动架构:事件溯源
- 命令和查询职责分离
- 响应式架构和Actor模型
- 具象状态传输(REST)
- 面向服务的架构(SOA)
- 微服务架构,其本质等同于DDD中的限界上线文
第3章 运用子域进行战略设计
什么是子域?
- 子域是整个业务领域的一部分
- 子域代表的是一个单一的,有逻辑的领域模型
- 一个明确的专业领域
子域类型
- 核心域(Core Domain) : 唯一的,定义明确的领域模型,要对它进行战略投资,并在一个明确的限界上下文中投入大量资源去精心打磨通用语言,必须把核心域打造成组织的核心竞争力;
- 支撑子域(Supporting Subdomain):建模场景提倡“定制开发”,会考虑外包的方式实现
- 通用子域(Generic Subdomain):可采购现成的
应对复杂性
- 子域可以作为讨论问题空间的工具
- 用网关屏蔽各子域的差异性
- 我们将逻辑模型当成一个子域对待,并且分开通用语言,可以避免系统形成大泥球
- 使用子域思考和讨论遗留系统,也可以避免形成大泥球
- 如果必须在同一个限界上下文中创建第二个模型,应该使用同一个完全独立的模块将该模型从核心域中分离出来;
第4章 运用上下文映射(Context Mapping)进行战略设计
当项目的核心域必须和其他限界上下文进行集成,这种集成关系在DDD中称为上下文映射(Context Mapping);
在DDD中,链接两个限界上下文之间的这条限定就代表了上下文映射。每个限界上下文中都有一种通用语言,通过上下文映射,将两个限界打通;
映射的种类
合作关系
- 每个团队各自负责一个限界上下文;
- 两个团队通过互相依赖的一套目标联合起来形成合作关系;比如金融云团队中,贷前、贷中、贷后,就是一种合作关系。
共享内核
- 两个/更多团队之间共享着一个小规模但却通用的模型;
- 团队必须就要共享的模型元素达成一致。如:万卡的core包,以及用户提供core服务;
共享内核:常见的方式就是将通用模块通过jar依赖的方式共享给所有上下文使用;
** 客户-供应商**
描述的是两个限界上下文之间和两个独立团队之间的一种关系。
供应商位于上游(U):供应商提供了客户必须的东西,同时也制定了获取的标准;
客户位于下游(D):依赖供应商提供的服务
如:支付、三方前置
跟随者
跟随者关系存在于上游团队和下游团队之间,上游团队没有任何动机满足下游团队的具体需求,也可以理解为发布-订阅模型。
比如:在万卡借贷的核心流程中,大数据/营销团队是万卡核心流程的跟随者。在核心业务流程中,生成的事件,不会考虑下游系统所需的字段,只是如果在过程中自己用到了,就带着,如果不用,不会为了下游的跟随者去获取对应的数据放到事件中。
万卡与万卡商城也是跟随者模型,万卡商城的客户依赖于万卡,流量也依赖于万卡。
防腐层
防腐层是最具防御性的上下文映射关系,下游团队在其通用语言(模型)和位于它上游的通用语言之间创建了一个翻译层。
常见的防腐层-API网关
金融云的前置就是一个典型的防腐层,将对接的机构的接口形式转成贷中、贷后需要的模型,屏蔽了差异性。
服务总线bus也是一个防腐层。
开放主机服务
开放式主机服务会定义一套协议或接口,让限界上下文可以被当作一组服务访问。
如:rest服务,万卡-金融云-风控都是根据开放主机服务交互。
特别是对外服务的openApi,每次升级都必须兼容或者保留原有的服务。
已发布语言 已经发布的接口定义的通用语言。
各行其道
没有标准,随时会变,不能作为强依赖。特别是舆情监控里,需要去爬取微博或者百度,或者淘宝,就是一个各行其道。
大泥球 如之前的万卡API,职责定义不够清晰,各种需求柔和。
以下操作可能会变为大泥球
- 越来越多的聚合因为不合理的关联和依赖而交叉污染;
- 对大泥球的一部分进行维护就会牵一发而动全身,解决问题就像“打地鼠”;
- 只有同时了解各个系统通用语言的人才能保持项目的稳定;
善用上下文关系
- 基于数据库的集成方式一定要避免(如果一定要,通过防腐层来隔离要去集成的和适配的模型)
三种可靠的集成类型
基于SOAP的RPC(dubbo也可以看成是一种)的集成
区别是:soap是一套标准,dubbo通过提供api的jar包来完成。
需要注意:
- 网络瘫痪;
- 网络延迟;
- 之间是紧耦合;
基于SOAP的RPC缺乏健壮性,网络或者服务出现问题,容易挂。dubbo的负载能解决部分问题。
基于restful http的集成
接口设计要注意:
- 对外屏蔽模型与逻辑;
基于消息机制的集成
- 使用消息机制是最健壮的集成方法之一;
- 可以消除阻塞性质耦合;
- 领域事件由限界上下文中的聚合(Aggregate)发布;
- 消费者不应该使用事件发布者定义的事件类型;
- 应保证至少一次投递;
- 接受者必须实现幂等接收;
上下文映射示例
增强事件与反向查询
增强事件:填充足够多的数据增强领域事件来满足所有的消费者的需求;
反向查下:领域事件中只推送业务主键,通过主键反查自己需要的信息;
- 事件发布时,只需要将自己持有的数据发布即可,可将事件结果进行状态包装,避免查库或远程调用后加工;(包装发布业务的稳定性)
- 在事件发布时,注意数据安全;
第5章 运用聚合进行战术设计
什么是实体?
一个实体模型就是一个独立的事务,每个实体都拥有一个唯一的标识符,可以将他的个体性所有其他类型相同或不同的实体区分开;
值对象
- 值对象是用来描述、量化或者测量一个实体。
- 一个值对象不是事物;
- 值对象,就是一个值(Value)
聚合
聚合是由一个或多个实体组成,其中一个实体被称为聚合根。
- 一个或多个实体;
- 值对象
- 聚合的根实体控制这所有聚集在其中的其他元素;
- 每个聚合都会形成保证事务一致性的边界;
事务
- 使用事务实现细节;
- 隔离对聚合的修改;
- 保证业务不变性;
- 在每一次操作中都保持一致;
- 事务边界由商业动机决定;
- 业务规则是事务的驱动力,最终决定在单次事务里,哪些对象必须是完整;
聚合的经验法则
聚合的四条基本规则:
- 在聚合边界内保护业务规则不变性
- 聚合要设计的小巧
- 只能通过标识符引用其他聚合
- 使用最终一致性更新其他聚合
在聚合边界内保护业务规则不变性
聚合的组成部分应该有业务最终决定;
聚合要设计的小巧
- 聚合的内存占用和事务包含范围应该相对较小
- 聚合的设计要满足单一职责
- 但也要防止过度设计导致项目的复杂度提升;
只能通过标识符引用其他聚合
- 标识符可以理解为业务的主键(这要根据实际场景考虑,防止数据重复获取)
- 保持聚合设计的小巧又高效
- 使用标识符使聚合可以使用任何类型的持久化机制轻松存储;
使用最终一致性更新其他聚合
- 通过领域事件完成最终一致性;
- 在完成最终一致性的同时,要处理消息的积压与丢失问题;
- 同时要考虑异常情况导致的中断;
建立聚合模型
贫血模型:模型好处了公有访问(Getter 和Setter)之外没有包含任何真正的业务行为;贫血模型在函数式编程时可以作为一种规范标准,因为函数式编程宣扬的是数据和行为的分离;
- 我们要把领域模型中的业务逻辑放到上层的应用服务中;
- 领域模型应该由持久化对象转化而来;
- 领域模型应该是当前业务逻辑所需数据的组合;
- 领域模型应该是数据与行为的一种聚合;
聚合设计的步骤:
- 为聚合根实体创建一个类(必须拥有全局唯一的标识符)
- 记录在查找聚合时必须用到的内在属性或者字段;
- 适当的屏蔽setter
- 添加聚合要实现的具体行为;
例如
`public class Auth`
`//授信项,聚合的值对象`
`private String itemName;`
`//授信状态`
`private String authState;`
`// 创建时间`
`private Date createTime;`
`//customerId+ tenantId 就是一个全局唯一标识符`
`//用户的customerId (不可变的值对象)`
`private CustomerId customerId;`
`//租户id(不可变的值对象)`
`private TenantId tenantId;`
`//构造函数中不带状态`
`Auth(CustomerId customerId,TenantId tenantId,String itemName)`
`this.customerId = customerId;`
`this.tenantId = tenantId;`
`this.itemName = itemName;`
``
`// 这里只有行为`
`public void init()`
`//初始化前的校验,需要将行为和数据入库操作分离`
`this.initValidate();`
`this.authState = "J1501";`
`//产生领域事件`
`publishEvent();`
``
`//授信成功`
`public void succ()`
``
`//授信失败`
`public void fail()`
``
`//只暴露Getter,因为一旦初始化不会改变`
`public Customer getCustomerId()`
`return customerId;`
``
`//只暴露Getter,因为一旦初始化不会改变`
`public TenantId getTenantId()`
`return tenantId;`
``
``
慎重选择抽象级别
- 软件模型是建立在一套业务行为方式的抽象上;
- 建模语言由领域专家表达;
- 软件模型要匹配领域专家的心智模型;
- 适当的抽象,防止太散就行组装会特别麻烦;
- 不要试图去解决无关紧要的无解问题;
- 不要预先满足未来的所有需求;
- 尽可能的控制范围,通过持续重构解决问题;
大小适中的聚合
要防止防止过度设计
- 聚合要设计的小巧(以满足业务为条件,不要过度);
- 聚合边界内保护业务不变性规则;
- 和领域专家确认,每个事件的响应时间(即时或延迟)
可测试的单元
聚合的设计要考虑可测试性。
可测试既可以保证业务的正确性,也可以保证后续持续重构的严重;
第6章 运用领域事件进行战术设计
领域事件是一条记录,记录这限界上下文中发生的对业务产生重要影响的事情;
注意点:
- 事件执行的顺序性(按业务逻辑必须保证);
- 事件消息丢失的问题;
设计、实现并运用领域事件
事件溯源
对所有发生在聚合实例上的领域事件进行持久化,把他们当做对聚合实例的记录;
通俗点讲,就是记录下log形成事件流,方便后续的追溯以及,事件的重试;也可以理解为流量回放;
要考虑存储的性能,以及快照恢复的时候;
第7章 加速和管理工具
主要介绍怎么加速DDD的设计和使用哪些工具或手段;
事件风暴
- 一种快速的设计技术;
- 领域专家和开发人员都可以参与到这个快节奏的学习过程;
- 聚焦业务和业务流程;
事件风暴步骤
- 通过创建一系列写在便利贴上的领域事件,快速梳理出业务流程;遵守的一些基本规则:
- 强调我们优先和主要关注的是业务流程;
- 把每个领域事件的名称卸载一张便利贴上;
- 把写好的便利贴按照时间顺序摆放在建模平面上(按照每个事件在领域中的发生的先后顺序从左到右排列)
- 按照业务流程,有些领域事件和其他事件并行发生,可以把这些事件摆放在同时发生的领域事件的下方;
- 在风暴讨论的这个步骤中,会在已有的或新的业务流程中发现问题点;
- 有时领域事件将导致一个需要执行的流程;
- 创建导致每个领域事件发生的命令;
领域事件由其他系统中所发生的事情引发,作为结果流入系统中。命令通常是某个用户操作的结果,命令的执行将导致领域事件的发生。
- 把命令和领域事件通过实体/聚合关联起来,命令在实体/聚合上执行并产生领域事件的结果。实体就是命令执行和领域事件触发的数据载体。
- 在建模平面上划出边界和表示事件流动的箭头连线。
- 识别用户执行操作所需的各种视图,以及不同用户的关键角色;
先以整体的视角画出核心流程,再以每一个节点深入梳理对应的具体逻辑。
在敏捷项目中管理DDD
运用SWOT分析法
想了解更多,请关注公众号
领域驱动设计软件核心复杂性应对之道读书笔记
问题:1、 何为领域驱动设计(DOMAINDrivenDESIGN)?2、 UBIQUITOUSLANGUAGE(领域通用语言)应该是如何去描述3、 作者:EricEvans第二部分模型驱动设计的构造块 第四章分离模型分层架构如图: 而主要的... 查看详情
《领域驱动设计:软件核心复杂性应对之道》读书笔记
1.EricEvans强调要聚焦于软件的核心领域,以它来驱动开发。软件能够在市场上卖出去。是因为它封装了别的软件所灭有的一些核心领域知识,这就是核心竞争力,是利润所在的地方,也是最值得下功夫的地方,再难也不能逃避。2... 查看详情
ddd领域驱动设计精要
本文算是《领域驱动设计》这本书的读书笔记,加上自己的一些读后感。网上有很多这本书的读书笔记,但是都是别人的,不如自己总结的理解深刻。建议大家在读这本书时结合《实现领域驱动设计》一起看,同时,一定要... 查看详情
网摘读书笔记----javascript语言精粹
原文链接:http://www.cnblogs.com/Cohlint/archive/2012/11/26/2788790.html1.6种值会为假(==false),分别是false,null,undefined,‘‘,0,NaN2.typeof有6种值,分别是‘number‘,‘string‘,‘boolean‘,‘undefined‘,‘function‘,‘object‘;其中typeo 查看详情
《nosql精粹》读书笔记
NoSQL数据库数据模型的一般分类:1.键值数据模型2.文档数据模型3.列族数据模型4.图数据模型常见NoSQL数据库:Redis,Cassandra,MongoDB,Neo4J,Riak...数据库应用趋势:1.由于数据量越来越大,大型系统的扩展方式由数据库在单一计算机上的... 查看详情
《javascript语言精粹》读书笔记——给类型增加方法一节的疑问
最近,在学习《JavaScript语言精粹》这本书,发现译者虽然有很好地翻译文章,却没有对文中有疑问的地方进行改正或加以注释。我接触JavaScript只有一年左右,可能无法很好的理解这门语言,而今天,读到第四章中4.7节—... 查看详情
读书笔记《测试驱动开发》一
单个类的开发原则:从类的小功能开始开发,在具体写实现代码之前,先写测试代码。通过先写测试代码,能确定类的指定功能较好用的接口。小函数开发周期:先将问题转为一个测试程序通过存根等方式尽... 查看详情
安全——《微服务设计》读书笔记
...它访问资源。 SAML和OpenIDConnect/OAuth2.0是企业领域中占据统治地位的单点解决方案。 & 查看详情
《领域驱动设计》阅读笔记第1章消化知识
ddd小白,一篇章节便能激起了心中涟漪,感慨之初,记于笔下。 第1章 消化知识 用醍醐灌顶、茅塞顿开来形容此章短短的文字,实不为过。简单介绍背景:旅游互联网,B2B,初创公司。产品设计-代码开发的衔接... 查看详情
《构建之法》读书笔记七
计算机领域有很多基本名词,比如说最常出现的,程序员都不太喜欢的——bug(缺陷)。 测试设计有两类方法:黑箱(BlackBox)和白箱(WhiteBox)。要注意的是,这是软件测试设计的方法,不是软件测试的... 查看详情
大话设计模式读书笔记——开闭原则
开闭原则在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”[1],这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品... 查看详情
《构建之法》读书笔记
...到软件的开发、运营和维护上的过程;软件工程包括一下领域:源代码管理+需求分析+程序设计+软件构建+软件测试+软件维护+生命周期管理等,广泛意义的软件工程,还包括用户体验、用户界面设计(UID)等;软件工程决定了软件... 查看详情
《微服务设计》读书笔记大纲
cha1:微服务的概念——《微服务设计》读书笔记cha2:微服务架构师的职责——《微服务设计读书笔记》cha3:建模:确定服务的边界——《微服务设计》读书笔记cha4:微服务集成——《微服务设计》读书笔记 ... 查看详情
读书笔记-js高级程序设计-第十五章使用canvas绘图
...有时候即使浏览器支持,操作系统如果缺缺乏必要的绘图驱动程序,则浏览器即使支持了也没用 <canvas>vardrawing=document.getElementById("drawing");if(drawing.ge 查看详情
javascript设计模式读书笔记=;行为型设计模式
全系列目录JavaScript设计模式读书笔记(一)=>创建型设计模式JavaScript设计模式读书笔记(二)=>结构型设计模式JavaScript设计模式读书笔记(三)=>行为型设计模式JavaScript设计模式读书笔记... 查看详情
javascript设计模式读书笔记=;结构型设计模式
全系列目录JavaScript设计模式读书笔记(一)=>创建型设计模式JavaScript设计模式读书笔记(二)=>结构型设计模式JavaScript设计模式读书笔记(三)=>行为型设计模式JavaScript设计模式读书笔记... 查看详情
2018年的读书清单
《别做正常的傻瓜》《人人都是架构师》《领域驱动设计和模式实践》《JavaScript设计模式与开放实践》《JavaScript正则表达式迷你书》《大话设计模式》(第4遍阅读)《算法导论》缓存,数据库优化库表拆分,负债均衡,消息队... 查看详情
javascript设计模式读书笔记=;创建型设计模式(代码片段)
全系列目录JavaScript设计模式读书笔记(一)=>创建型设计模式JavaScript设计模式读书笔记(二)=>结构型设计模式JavaScript设计模式读书笔记(三)=>行为型设计模式JavaScript设计模式读书笔记... 查看详情