书山天道-领域驱动设计-第一遍通读
目的:学习领悟 DDD 设计思想
2021-02-11 00:19:39
软件设计风格遵循”责任驱动设计“原则,”契约式设计”思想。

key point :
需要着眼于模型中的元素且将它们视为一个系统.
需要将领域对象与系统中的其他功能分离,这样就能够避免将领域概念和其他只与软件技术相关的概念搞混了。
分离领域的复杂技术,对于能否成功运用领域建模原则起着非常关键的作用.
要想创建出能够处理复杂任务的程序,需要做到关注点分离--使设计中的每个部分都得到单独的关注。
在分离的同时,也需要维持系统内复杂的交互关系。
成功架构的4个概念层
| 用户界面层(表示层) | 负责向用户显示信息和解释用户指令,用户既可以是另一个计算机系统,不一定是使用用户界面的人 |
| 应用层 |
定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大, 也是与其他系统的应用层进行交互的必要渠道。 应用层要尽量简单,不包含业务规则或者知识,而只是为下一层的领域对象协调工作,分配工作,使他们互相协作。 它没有反映业务情况的状态,但是却可以具有另一种状态,为用户或 程序显示某个任务的进度。 |
| 领域层(或 模型层) |
负责表达业务概念,业务状态信息以及业务规则。 尽管保存业务状态的技术细节是由基础设施层实现的,但是反映业务情况的状态是由本层控制且使用的。 领域层是业务软件的核心。 |
| 基础设施层 |
为上面各层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件... 还能够通过架构框架来支持4个层次间带的交互模式。 |
将领域层分离出来是实现 Model-Drivern Design 的关键。
领域对象应该将重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题,也无需管理应用任务等内容。
这使得模型的含义足够丰富,结构足够清晰,可以捕捉到基本的业务只是,并有效地使用这些知识。
在连接各层的同时不影响分离带来的好处,这是很多模式的目的所在。
层与层,上层可以直接操作下层元素,通过调用下层元素的公共接口,保持对下层元素的引用(至少是暂时的),以及采用常规的交互手段。
下层与上层进行通信(不只是回应直接查询),使用架构模式连接上下层,如 回调模式或Observers 模式。
最早将用户界面层与 应用层 和 领域层 相连的模式 MVC 框架。
Appliccation cooreidnator 应用协调器 是连接应用层的一种方法。
只要连接方式能够维持领域层的独立性,保证在设计领域对象时不需要同时考虑可能与其交互的用户界面,那么这些连接方式都是可用的。
基础设施层不会发起领域层的操作,它处于‘领域层’之下,不包含其所服务的领域的知识,这种技术能力最常以SERVICE的形式提供。
如果一个应用程序需要发送E-mail, 一些消息发送的接口可以放在基础设施层中,这样,应用层中的元素就可以请求发送消息了.
这种解耦使程序的功能更加丰富。
消息发送接口可以连接到E-mail发送服务,或其他可用的服务,
这种方式最主要的好处是简化了应用层,使其只专注于自己所负责的工作:知道 何时该发送消息,而不用操心该怎么发送。
应用层和领域层可以调用基础设施层所提供的Service,如果Service的范围选择合理,接口设计完善,通过把详细行为封装到服务接口中,
调用程序就可以保持与Servic的松散连接,自身也会很简单。
并不是所有的基础设施都是以可供上层调用的 Service的形式出现的,
有些技术组件被设计成直接支持其他层的基本功能(如为所有的领域对象提供抽象基类),且提供关联机制(如MVC及类似框架的实现)
这种“架构框架”对于程序其他部分的设计有着更大的影响。
表示模型的三种模型元素模式:Entity, Value object, Service
一个对象是表示某种具有连续性和标识的事物(可以跟踪它所经历的不同状态,甚至可以跨不同的实现跟踪它)
还是用来描述某种状态的属性呢?
这是 Entity 与 Value Object 之间的根本区别。
领域中有一些方面适合用 动作 或 操作 来表示,这比用对象表示更加清楚。这方面最好用 Service 来表示,而不应把
操作的责任强加到 Entity 或 Value Object 上。
Service 是应客户端请求来做某事。
当对软件要做的某项 无状态的活动进行建模时,就可以将该活动作为一项 Service.
关联:
模型中每个可遍历的关联,软件中都要有同样属性的机制.
至少有3中方法可以使得关联更易于控制
1)规定一个遍历方向
2)添加一个限定符,以便有效地减少多重关联
3)消除不必要的关联。
限定多对多关联的遍历方向可以有效地将其实现简化为一对多关联,从而得到一个简单的多的设计。
关联的约束,将这些约束添加到模型和实现中。可以使模型更精确,使实现更易于维护. 对模型进行精化.
Entity
满足两个条件即可:1.在整个声明周期中具有连续性,2.区别并不是由那些对用户非常重要的属性决定的.
当一个对象由其标识(而不是属性)区分时,那么在模型中应该主要通过标识来确定该对象的定义。
使类定义变得简单,并集中关注生命周期的连续性和标识。
定义一种区分每个对象的方式,这种方式应该与其形式和历史无关。
要格外注意那些需要通过属性来匹配对象的需求。
在定义标识操作时,要确保这种操作为每个对象生成唯一的结果,这可以通过附加一个保证唯一性的符号来实现。
这种定义标识的方法可能来自外部,也可能是由系统创建的任意标识符,但在模型中必须是唯一的标识。
模型必须定义出“符合什么条件才算是相同的事物”。
2021-01-23
Service
可以控制领域层中的接口粒度,避免客户端与 Entity 和 Value object 耦合.
中等粒度,无状态的Service 更容易被复用,在简单的接口背后封装了重要的功能. 细粒度的对象可能导致分布式系统的消息传递效率低下.
应用层负责对领域对象的行为进行协调,因而细粒度的领域对象可能会把领域层的知识泄露到应用中.
产生的结果是应用层不得不处理复杂的,细致的交互,从而使得领域知识蔓延到应用层或用户界面代码中,而领域层会丢失这些知识。
明智地引入领域层服务有助于在应用层和领域层之间保持一条明确的界限.
有利于保持接口的简单性,便于客户端控制并提供了多样化的功能。提供了一种在大型分布式系统便于对组件进行打包的中等粒度的功能。
有时,Service 是表示领域概念的最自然的方式。
与分离特定职责的设计决策相比,提供对Service的访问机制的意义并不是十分重大,一个”操作“ 对象可能足以作为 Service 接口的实现,
很容易编写一个简单的 Singleton 对象来实现对 Service 的访问。
这些对象只是Service接口的提供机制,而不是有意义的领域对象。
只有当真正需要实现分布式系统或充分利用框架功能的情况下才应该使用复杂的架构.
Module
提供了两种观察模型的方式
1. 在 Module 里查看细节, 而不会被整个模型淹没。
2. 观察 Module 之间的关系,而不考虑其内部细节。
Module 作为一种更粗粒度的建模和设计元素,采用低耦合高内聚原则更为重要。
只有以模型为中心进行思考,才能得到更深层次的解决方案。
对象的一个最基本概念是将数据和操作这些数据的逻辑封装在一起。
除非真正有必要将代码分布到不同的服务器上,否则就把实现单一概念对象的所有代码放在同一模块中(如果不能放在同一个对象中的话)
精巧的技术打包方案会产生如下两个代价:
1. 如果框架的分层惯例把实现概念对象的元素分的很零散,那么代码将无法再清楚地表示模型。
2. 人的大脑把划分后的东西还原成原样的能力是有限的,如果框架把人的这种能力都耗尽了,那么领域开发人员就无法再把模型还原成有意义的部分了.
对象模型可以解决很多实际的软件问题,但有些领域不适合用封装了行为的各种对象来建模。
涉及大量数学问题的领域或者受全局逻辑推理控制的领域就不适合使用面向对象的范式。
当领域的主要部分明显属于不同的范式时,明智的做法时用适合各个部分的范式对其建模,并使用混合工具集来进行实现。
当领域的各个部分之间的互相依赖性较小时,可以把用另一种范式建立的子系统封装起来。
例如,只有一个对象需要调用的复杂数学计算。将业务规则引擎或工作流引擎这样的非对象组件集成到对象系统中的动机。
非对象的技术基础设施,最常见的就是关系数据库。
Model driven design 不一定时面向对象的,确实需要一种富有表达力的模型结构实现,无论时对象,规则还是工作流.
将各个部分紧密结合在一起的最有效工具就是健壮的 Ubiquitous language ,它是构成整个异构模型的基础。坚持在两种环境中使用
一致的名称,讨论,有助于消除两种环境间的鸿沟。
在将非对象元素混合到以面向对象为主的系统中,要遵循以下4条经验规则:
- 不要和实现范式对抗。找到适合于范式的模型概念
- 把通用语言作为依靠的基础. 即使工具之间没有严格联系时,语言使用上的高度一致性也能防止各个设计部分分裂。
- 不要一味依赖UML。 UML确实有一些特性很适合表达约束,但并不是在所有情况下都适用。其他风格的图形或简单的语言描述比牵强附会地适应某种对象视图更好。
- 保持怀疑态度。工具是否真正有用武之地。不能因为存在一些规则,就必须适用规则引擎。
关系范式 是范式混合的一个特例。关系数据库与对象模型的关系是其他关系更紧密。
2021-01-24
领域对象的生命周期
对象分类
1. 简单的临时对象,仅通过构造函数来创建,用来做一些计算,然后由垃圾收集器回收,要简单化处理
2.有些对象具有更长的生命周期,其中一部分时间不是在活动内存中度过。它们与其他对象具有复杂的依赖性,会经历一些
状态变化,在变化时要遵守一些固定规则。

挑战
1. 在整个生命周期中维护完整性
2.防止模型陷入管理生命周期复杂性造成的困境中。
三种模式解决这些问题
1. Aggregate
2. 生命周期开始阶段,Factroy 来创建和重建复杂对象和Aggregate,从而封装它们的内部结构。
3.在生命周期的中间和末尾使用Repository来提供查找和检索持久化对象并封装庞大基础设施的手段。
Aggregate
要知道一个由其他对象组成的对象从哪里开始,又到何处结束?
在任何具有持久化数据存储的系统中,对数据修改的事务必须要有范围,而且要有保持数据一致性的方式,
要保持数据遵守固定规则。
数据库支持各种锁机制,可以编写一些测试来验证。但这种特殊的解决方案分散了人们对模型的注意力,很快就会
回到”走一步,看一步”的老路上来。
要对领域有深刻的理解:例如,要了解特定类实例之间的更改频率这样的深层次因素。
需要找到一个使对象间冲突较少而固定规则联系更紧密的模型。
表面上看这个问题使数据库事务方面的一个技术难题,但它的根源却在模型,归根结底是由于模型中缺乏明确定义的边界。
从模型得到的解决方案将使模型更易于理解,并使设计更易于沟通。
当模型被修改时,将引导我们对实现做出修改。
定义模型中的所属关系,简单但严格的系统,其包括一组用于实现事务(这些事务用来修改对象及其所有者)的规则.
固定规则 invariant 是指在数据变化时必须保持一致性规则,其涉及 Aggregate 成员之间的内部关系,而任何跨越
Aggregate的规则将不要求每时每刻都保持最新状态。
通过 事件处理,批处理或其他更新机制,这些依赖会在一定时间内得以解决,但在每个事务完成时,Aggregate内部所应用
的固定规则必须得到满足.
为了实现这个概念上的Aggegate,需要对所有事务应用一组规则。
- 根Entity 具有全局标识,它最终负责检查固定规则。
- 根Entity具有全局标识,边界内的Entity具有本地标识,这些标识只在Aggregate内部才是唯一的.
- Aggregate外部的对象不能引用除根Entity之外的任何内部对象。根Entity可以把对内部Entity的引用传递给它们,但这些对象只能临时使用这些引用,而不能保持引用。根可以把一个Value object的副本传递给另一个对象,而不必关心它发生什么变化,因为它只是一个Value,不再与Aggregate有任何关联。
- 只有Aggregate的根才能从直接通过数据库查询获取,其他对象必须通过遍历关联来发现。
- Aggregate内部的对象可以保持对其他Aggregate根的引用。
- 删除操作必须一次删除Aggregate边界之内的所有对象。( 利用垃圾收集机制,由于除了根以外的其他对象都没有外部引用,因此删除了根以后,其他对象均会被回收 )
- 当提交对Aggregate边界内部的任何对象的修改时,整个Aggregate的所有固定规则都必须被满足。
将 Entity 和 Value object 分门别类地聚集到 Aggregate 中,并定义每个Aggregate 的边界.
在每个Aggregate中,选择一个Entity作为根,并通过根来控制对边界内其他对象的所有访问。
只允许外部对象保持对根的引用。
对内部成员的引用可以被传递出去,但仅在一次操作中有效。
由于根控制访问,因此不能绕过它来修改内部对象。
这种设计有利于确保Aggregate中的对象满足所有固定规则,也可以确保在任何状态变化时Aggregate作为一个整体满足固定规则。
P88 PurchaseOrder 与 采购项 需继续理解
Aggregate 强制了PO与采购项之间符合业务实际的所属关系,PO和采购项的创建及删除很自然地联系在一起,而Part的创建和删除却是独立的.
模式Factory
当创建一个对象或创建整个Aggregate时,如果创建工作很复杂,或者暴露了过多的内部结构,则可以使用Factory进行封装。
复杂对象的创建是领域层的职责,然而这项任务并不属于那些用于表示模型的对象。
有些情况下,对象的创建和装配对应于领域中的重要事件,如“开立银行账户”。
但一般情况下,对象的创建和装配并没有什么意义,只不过是实现的一种需要。
要在领域设计中增加一种新的构造,不是Entity, Value Object, 也不是service. 不对应与模型中的任何事物,确实又承担着领域层中的部分职责。
需要一种更加抽象,且不与其他对象发生耦合的构造机制,这就是factory. 是一种负责创建其他对象的程序元素.
正如对象的接口应该封装对象的实现一样(从而使客户无需知道对象的工作机理就可以使用对象的功能)
Factory封装了创建复杂对象或 Aggregate所需的知识。 提供了反映客户目标的接口,以及被创建对象的抽象视图。
提供一个封装所有复杂装配操作的接口,而且这个接口不需要客户引用要被实例化的对象的具体类。
在创建Aggregate时要把它作为一个整体,并确保它满足固定规则。
Factory 时领域设计的重要组件。正确使用Factory有助于保证 Model-Driven design 沿着正确的轨道前进。
Factory method, Abstract Factory, Builder
任何好的工厂都需满足以下两个基本需求。
1. 每个创建方法都是原子的,要保证被创建对象或Aggregate 的所有固定规则。Factory生成的对象要处于一致的状态。在生成
Entity时,意味着创建满足所有固定规则的整个Aggregate,但在创建完成后可以向聚合添加可选元素。在创建不变的Value object
意味着所有属性必须被初始化为正确的最终状态。
如果Factory通过其接口收到了一个创建对象的请求,而它又无法正确地创建对象,应该抛出一个异常,或采用其他机制,以确保不会返回
错误的值。
2.Factory应该被抽象为所需类型,而不是所要创建的具体类。
1. Factory 选择及其应用位置 p91
2. 有些情况下只需使用构造函数 p93
3. 接口的设计, 满足 每个操作必须是原子的, Factory 将与其参数 发生耦合. p94
4. Factory 可以将固定规则的检查工作委派给被创建对象,通常是最佳选择. (此处细读) p95
5. Entity Factory 与 Value Object Factory 两个方面的不同:
1. value object ,factory 操作必须得到被创建对象的完整描述。
2. entity factory, 只需具有构造有效 Aggregate 所需的那些属性. 对于固定规则不关心的细节,可以之后再添加.
6. 重建已存储的对象
重建对象的 Factory 与用于创建对象的 Factory 类似
1) 用于重建对象的 Entity Factory 不分配新的跟踪 ID. 标识属性必须是输入 Factory 输入参数的一部分。
2) 当固定规则未被满足时,重建对象的Factory 采用不同的方式进行处理. 重建比创建要复杂。 基于对象已存在的事实。需要修复策略。
恰当地结合搜索与关联将会得到易于理解的设计.
Repository
repository 是一个简单的概念框架,用来封装这些解决方案. p100
repository将某种类型的所有对象表示为一个概念集合(通常是模拟的)。行为类似于集合(collection),只是具有更复杂的查询功能。
在添加或删除相应类型的对象时, Repository的后台机制负责将对象添加到数据库中,或从数据库中删除对象。
这个定义将一组紧密相关的职责集中在一起,这些职责提供了对 Aggregate 根的整个生命周期的全程访问.
查询
接口设计的选择 p101
在一些需要执行大量查询的项目上,可以构建一个支持更灵活从查询的Repository 框架。
基于Specification 规格的查询是将Repository通用化的好办法。
客户可以使用规格来描述(也就是指定)它需要什么,而不必关心如何获得结果。在这个过程中,可以创建一个对象来实际执行筛选操作。
即使一个Repository 的设计采取了灵活的查询方式,也应该允许添加专门的硬编码查询。这些查询作为便捷的方法,可以封装常用查询或不返回对象
如返回的是选中对象的汇总计算的查询。
不支持这些特殊查询方式的框架有可能会扭曲领域涉及,或是干脆被开发人员弃之不用。
需要谨记的注意事项:
1. 对类型进行抽象,含有所有特定类型的所有实例,但并不意味着每个类都需要有一个repository, 类型可以是一个层次结构中的抽象超类。
类型可以是一个接口,接口的实现者并没有层次结构上的关联,也可以是一个具体类。
2. 充分利用与客户解耦的优点。
3. 将事务的控制权留给客户。只有客户才有上下文,从而能够正确地初始化和提交工作单元。Repository 不插手事务控制,事务管理会简单很多。
key poing
1. p119 能够在不发生争用的情况下输入 Handling Event 是一项重要的应用程序需求。
在 Delivery History 中不使用Handling Event 的集合,而是用一个查询来代替它,这样在添加Handling Event时就不会在其自己的Aggregate之外引起
任何完整性问题。如此修改之后,这些事务就不再受到干扰。
如果有很多 Handling Event 同时被录入,而相对只有很少查询,这种设计更加高效。
如果使用关系数据库作为底层技术,可以设法在底层使用查询模拟集合。使用查询来代替集合可以较少维护Cargo和Handling Event之间循环引用一致性的难度.
2. Anticorruption layer 连接两个系统, 不要直接交互,否则必须适应另一个系统的设计。 连接另一个系统的接口,用一个 xxxChecker xx检查器的类来实现这些service。
类名反映了它在系统中的职责。 p123
2021-01-30 18:26:38
两个系统的交互,定义Cargo的“类型”, 重新抽象与之交互的系统,使模型更加丰富. P125
分析模式可以为建模方案提供思路,Enterprise Segment 企业部门单元,是一组维度,定义了一种对业务进行划分的方式.
在领域模型和设计中增加一个 Enterprise segement 的类,是一个Value object,每个 Cargo都必须获得一个 Enterprise segment 类。
要想成功地开发出实用的模型,需要注意以下3点:
1. 复杂巧妙的领域模型使可以实现的,值得花费力气。
2.模型需要不断的重构,需要领域专家和开发
3.要实现并有效地运用模型,需要精通设计技巧。
建模本质上是非结构化的,要跟随学习与深入思考所指引的一切道路,然后据此重构,才能得到更深层的理解.
改进领域模型的具体思考方式以及可实现这些领域模型的设计方法。 P129
深层模型能够穿过领域表象,清楚地表达出领域专家的主要关注点以及最相关的知识。以上定义并没有涉及抽象,
事实上,深层模型通常含有抽象元素,但在切中问题核心的关键位置也同样会出现具体元素。
《分析模式》 chapter 6
概念建模
1. 约束是概念模型中非常重要的类别。它们通常是隐含的,将它们显式地表现出来可以极大地提高设计质量.
将约束条件提取到其自己的方法中,这样就可以通过方法名来表达约束的含义,从而在设计中显示地表现出这条约束.
这种方式也为约束的扩展提供了空间。
有时,约束条件无法用单独的方法来轻松表达。或者,即使方法自身能够保持简单性,但它可能会调用一些信息,
但对于对象的主要职责而言,这些信息毫无用处。这些规则可能就不适合放到现有对象中.
注意以下信号:
1.计算约束所需的数据从定义上并不属于所处的对象。
2.相关规则在多个对象中出现,造成了代码重复或者不属于同一族的对象之间产生了继承关系。
3.很多设计和需求讨论是围绕这些约束进行的,而在代码中,它们却隐藏在过程代码中.
如果约束的存在掩盖了对象的基本职责,或者约束在领域中非常突出但在模型中却不明显,可以将其提取到一个显式对象中,
甚至可以把它建模未一个对象和关系的集合。
将过程建模为领域对象
Service 是显示表达某种过程的一种方式,还将异常复杂的算法封装起来.
如果过程的执行有多种方式,将算法或其中的关键部分放到一个单独的对象中,这样选择不同的过程就变成了选择不同的对象,每个对象都表示
不同的 Strategy.
约束和过程是两大类模型概念,一旦被视为模型元素,可让设计变得更加清晰。
Specification 规则 用于表达特定类型的规则的精确方式,它把这些规则从条件逻辑中提取出来,并在模型中把它们显式地表示出来。
Specification 最有价值的地方在于它可以将看起来完全不同的应用功能统一起来.出于以下3个目的中的一个或多个,可能需要指定对象的状态。
1,验证对象,检查它是否满足某些需求或者是否已经为实现目标做好了准备。
2.从集合中选择一个对象,如查询满足条件的对象。
3.指定创建新对象时必须满足某种需求。
三者在概念层面上来讲是相同的。如果没有诸如Specification,相同的规则可能会表现为不同的形式,甚至可能是相互矛盾的形式。这样就会丧失
概念上的一致性。
2021-01-31
柔性设计 supple design
1. Intension-Revealing interfaces 释义命名选择器 类型名称,方法名称,和参数名称组合在一起。
整个领域可以被划分到独立的模块中,并用一个表达了其用途的接口把它们封装起来。
Cohesive Mechanism 和 Generic subdomain
2. Side-Effect-Free function
令一个方法的执行结果变得易于预测。复杂的逻辑可以在 Side-Effect-Free Function 安全执行,
而改变系统状态的方法可以用 Assertion 来刻画。
模式:
1. Intension-Revealing interfaces 释义命名选择器 类型名称,方法名称,和参数名称组合在一起。
在领域的公共接口中,可以把关系和规则表述出来,但不要说明规则是如何实施的;
可以把事件和动作描述出来,但不要描述它们是如何执行的;
可以给出方程式,但不要给出解方程式的数学方法;
可以提出问题,但不要给出获取答案的方法。
单元测试的目标,从客户开发人员的角度来研究以下Paint对象的接口设计。
花时间编写这样的测试是非常有必要的,因为它可以反映出我们希望以哪种方式与这些对象进行交互。
在领域驱动设计中,我们希望看到有意义的领域逻辑。
如果代码只是在执行规则后得到结果,而没有把规则显示地表达出来,那么我们就不得不一步一步地去思考软件的执行步骤。
那些只是运行代码然后给出结果的计算--没有显式地把计算逻辑表达出来,也有同样的问题。
如果不把代码与模型清晰地联系起来,很难理解代码的执行效果,也很难预测修改代码的影响。
对规则和计算进行显式的建模。实现这样的对象要求我们深入理解计算或规则的大量细节。
命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的。要与 Ubiquitous language 保持一致,
以便团队成员可以迅速推断出它们的意义。
在创建一个行为之前先为它编写一个测试,这样可以促使你站在客户开发人员的角度上思考它。
所有的复杂机制都应该封装到抽象接口的后面,接口只表明意图,而不是表明方式。
2. Side-effect-free function
宽泛地把操作分成两个大的类别: 命令和查询
查询:从系统获取信息,查询的方式可能只是简单地访问变量中的数据,也可能是用这些数据执行计算。
命令:也称为修改器,是修改系统的操作,如设置变量。
“副作用”,任何对系统状态产生的影响都叫副作用。
返回结果而不产生副作用的操作成为函数。
一个函数可以被多次调用,每次调用都返回相同的值,一个函数可以调用其他函数,而不必担心这种嵌套的深度.
减少命令产生的问题:
1.把命令和查询严格地放在不同的操作中,确保导致状态改变的方法不会返回领域数据,并尽可能简单,在不引起热河
可观测到副作用的方法中执行所有的查询和计算。
2. 部队现有对象做任何修改,创建并返回一个VALUE OBJECT, 用于表示计算结果。Value object 可以在一次查询的响应中
被创建和传递,然后被丢弃。不像Entity,实体的生命周期是受到严格管理的.
Value Object 是不可变的,意味着除了在创建期间调用的初始化程序之外,它们的所有操作都是函数。
像函数一样,Value object 使用起来很安全,测试也很简单。
如果一个操作把 逻辑或计算 与 状态改变混合在一起, 应该把这个操作重构为两个独立的操作
从定义上看,把副作用隔离到简单的命令方法中的做法仅适用于 Entity。
在完成了修改和查询的分离之后,进一步重构,把复杂计算的职责转移到Value object 中, 通过派生出一个Value object
(而不是改变现有状态),或者通过把职责完全转移到一个Value object 中,完全可以消除副作用。
Assertion
如果操作的副作用仅仅是由它们的实现隐式实现的,那么在一个具有大量相互调用关系的系统中,起因和结果会变得一团糟。
理解程序的唯一方式就是沿着分支路径来跟踪程序的执行。
封装完全失去了价值,跟踪具体的执行也使抽象失去了意义。
Assertion 只声明状态,而不声明过程。
把复杂的计算封装到 Side-effec-free function 中可以简化问题,但实体仍然会留有一些有副作用的命令,使用这些entity的人必须
了解使用这些命令的后果。
使用 Assertion 可以把副作用明确地表示出来,使他们更易于处理.
把 固定规则,前置条件,后置条件清楚地表述出来,就能够理解使用一个操作或对象的后果。
丢失概念 p179
Conceptual contour 概念轮廓
"是根据当前模型和代码中的特定关系做出的权宜之计,还是反映了底层领域的某种轮廓“
Whole value
寻找概念上有意义的单元,可以使得设计既灵活又易懂。
如果是一个连贯的整体操作,就作为整体来实现,不要拆分成两个步骤,不要在同一个操作中进行下一个步骤。
把设计元素(操作,接口,类和Aggregate)分解为内聚的单元,对领域中一切重要划分的直观认识也要考虑在内。
在连续的重构过程中观察发生变化和保持稳定的规律性,并寻找能够解释这些变化模式的底层 Conceptual Contour.
使模型与领域中那些一致的方面(正是这些方面使得领域成为一个有用的知识体系)相匹配。
Standalone class
每个关联都是一种依赖,要想理解一个类,必须理解它与哪些对象有联系。
与这个类有联系的其他对象还会与更多的对象发生联系,而这些联系也必须搞清楚,
每个方法的每个参数的类型也是一个依赖,每个返回值也是一个依赖。
应该对每个依赖关系提出质疑,直到证实它确实表示对象的基本概念为止。
这个仔细检查依赖关系的过程从提取模型概念本身开始。然后需要注意每个独立的关联和操作。
仔细选择模型和设计能大幅减少依赖关系---常常减少到零。
当两个对象具有自然的紧密耦合关系时,这两个对象共同设计的多个操作实际上能够把它们的关系本质
明确地表示出来。我们的目标不是消除所有依赖,而是消除所有不重要的依赖。
尽力把复杂的计算提取到 Standalone class 中,一种方法时从存在大量依赖的类中将 Value object 建模出来。
低耦合是减少概念过载的最基本办法,独立的类是低耦合的极致。
Closure of operation 闭合操作
在适当的情况下,在定义操作时让它的返回类型与其参数的类型相同,
如果实现者的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数和返回值应该与实现者有相同的类型。
这样的操作就是在该类型的实例集合中的闭合操作。
闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖。
减少依赖的同时保持丰富接口。
声明式设计风格
一旦你的设计中有了 Intention-revealing interface, side-effect-free function, assertion 就具备了使用声明式设计的条件。
有了可以组合在一起表达意义的元素,并且使其作用具体化或明朗化,甚至是完全没有明显的副作用,就可以获得声明式设计的很多益处。
Specification 是由谓词 predicate 形式化概念演变而来。
包含
包容等于逻辑蕴涵 logical implication 使用传统的符号 A -> 表示声明A蕴涵声明B , 因此 ,如果A为真,则B也为真.
subsuming specification p197
切入问题的角度
澄清代码意图,使得代码的影响变得显而易见,且解除模型元素的耦合。
必须选择具体的目标。
1. 分割子领域
重点突击某个部分,使设计的一个部分真正变得灵活起来,这比分散精力泛泛地处理整个系统要有用得多。
看出适合用哪种方法处理,那么就把它们提取出来加以处理。如果模型的某个部分可以被看作是专门的数学,可以把这部分分离出来。
如果应用程序实施了某些用来限制状态改变的复杂规则,那么可以把这部分提取到一个单独的模型中,或者提取到一个允许声明规则的简单框架中。
剩下的模型中,有的部分是用声明式的风格来编写的-这些可能是根据专门数学或验证框架编写的声明,或者是子领域所采用的任何形式。
2021-02-01
应用分析模式
分析模式是一种概念集合,用来表示业务建模中的常见结构。
分析模式并不是解决方案,用来设计特定领域中的模型。
减少缠杂不清的依赖的第一步是通过 引入一个新对象来使这些规则明朗化。 p212
在依赖性设计中,最复杂的部分是更新的时机和控制措施。
1) "主动触发“是最直接的方式,但通常也最不实用。每当一个Entry被插入到Account中时,它立即就触发过账规则,并立即进行所有更新。
2)"基于Account的触发“,它允许延迟处理。在过后的某个时刻,向Account发送一条消息,令其触发规则,来处理自上一次触发以来所插入
的所有Entry.
3) 最后,”基于过账规则的触发“, 由外部代理来启动,它通知过账规则触发,
过账规则负责查找自从上次触发依赖插入到其输入Account中的所有Entry.
每组特定的规则都需要有一个明确定义的”启动点“(应该在何时启动),还要定义由谁 负责查找插入到输入的Account中的Entry.
将这三种触发模式添加到 Ubiquitous language 中对于成功使用这种模式具有至关重要的意义,这与模型对象定义本身同等重要。
触发的概念就不再模糊,而且还能直接指导决策。从而获得一组明确定义的可选方案。
揭示出了一个很容易被忽略的重点,丰富了我们的词汇。
应该把所有分析模式的知识融入到知识消化和重构的过程中,从而形成更深刻的理解,并促进开发。
有一个误区要避免,当使用众所周知的分析模式的术语时,不管其表面形式的变化有多大,都不要改变它所表示的基本概念。
一是模式中蕴涵的基本概念将帮助我们避免问题。
二是使用被广泛理解或至少是被明确解释的属于可以增强Ubiquitous language。
设计模式
在一定的抽象层次上讨论模式,并不是指像链表和散列表那样可以被封装到类中并供人们直接重用的设计,也不是用于整个
应用程序或子系统复杂的,领域特定的设计。是对一些交互的对象和类的描述,通过定制这些对象和类来解决特定上下文中
的一般设计问题。
从代码的角度来看它们是技术设计模式,从模型的角度来看它们就是概念模式。
Strategy (Policy)
在对过程进行建模时,经常会发现过程有不止一种合理的实现方式,而如果把所有的可选项都写到过程的定义中,定义就会变得
臃肿而复杂,可供我们选择的实际行为也会因为混杂在其他行为中而显得模糊不清。
把这些选择从过程的主体概念中分离出来,这样既能够看清主体概念,也能更清楚地看到这些选择。
需要把过程中的易变部分提取到模型的一个单独的”策略“对象中,
讲规则与它所控制的行为区分开.按照Strategy设计模式来实现规则或者替换的过程。
策略对象的多个版本表示完成过程的不同方式。
作为设计模式的Strategy侧重于替换不同算法的能力,而当其作为领域模式时,其侧重点则是表示概念的能力,这里的概念
通常是指过程或策略规则。
每个计算中都有条件判断,判断最快或最便宜的逻辑分散在程序各处。把新标准添加进来时会麻烦。
把这些起调节作用的 参数分离到Strategy中,可以被明确地表示出来,并作为参数传递给Routing Service.
用一种完全相同的,无需进行条件判断的方式来处理所有请求了,按照 xxx policy 的计算。提供丰富的功能。 P219
2021-02-02 20:13:26
当在领域中应用任何一种设计模式时,首先关注的问题应该是模式的意图是否确实适合领域概念。
以递归的方式遍历一些相互关联的对象确实比较方便,但它们是否真的存在整体-部分层次结构?
你是否发现可以通过某种抽象方式把所有部分都归到同一概念类型中?
如果你确实发现这种抽象方式,那么使用Composite可以使模型这一部分变得更清晰。同时使你能够借助
设计模式所提供的那些经过深思熟虑的设计及实现的考量。
定义一个把Composite的所有成员都包含在内的抽象类型。在容器上实现那些查询信息的方法时,
这些方法返回由容器内容所汇总的信息。
而“叶”节点则基于它们自己的值来实现这些方法。
客户只需使用抽象类型,而无需区分“叶”和容器。
无论在较小的部分还是较大的部分,都可以对这些部分提出一些有意义的问题。
这些问题能够透明地反映出它们的构成情况。
这种严格的对称 是组合模式具有强大能力的 关键所在。
通过重构得到更深层的理解
注意3点
1. 以领域为本
2. 用一种不同的方式来看待事物
3. 始终坚持与领域专家对话
获得深层理解的重构:一开始可能是解决代码中的问题--复杂或笨拙的代码。
根源在于领域缺少一个概念,
或者某个关系发生了错误。
模型的语言没有与领域专家保持一致,或者新需求不能被自然地添加到模型中。
不管根源是什么,下一步是要找到一种能够使模型表达变得更清楚和更自然的改进方案。
从书籍和领域自身的其他知识源获得思路。业务人员的概念组织,一些有用的抽象。
从分析模式中汲取他人的经验。这些经验对于帮助我们读懂领域起到了一定的作用。
分析模式是专门针对软件开发的,分析模式可以提供精细的模型概念,帮助我们避免很多错误。
随着零散知识的归纳,必须同时处理模型关注点和设计关注点。
当设计模式既符合实现需求,又符合模型概念时,通常就可以在领域层中应用这些模式。
当一种常见的形式体系(如算数逻辑或谓词逻辑),与领域的某个部分非常符合时,可以把这个部分
提取出来,并根据它来修改形式系统的规则。
这可以产生非常简练且易于理解的模型。
重构的时机
1. 设计没有表达出团队对领域的最新理解
2.重要的概念被隐藏在设计中。
3. 发现了一个能令某个重要的设计部分变得更灵活的部分。
保持模型的完整性
用于识别,沟通和选择模型边界及关系的技术。
1 首先从描绘项目当前的范围开始。
Bounded context 限界上下文 定义了每个模型的应用范围。
context Map 则给出了限界上下文以及它们之间关系的总体视图。
确定了context 的边界之后,仍需要持续集成这种过程,能使模型保持统一.
2. 在这个稳定的基础之上,就可以开始实施那些在界定和关联 Context 方面更有效地策略了--
通过共享内核 shared kernel 来紧密关联上下文,到那些各行其道的Separate ways 地进行松散耦合的模型.

两个系统的数据格式是不同的,因此需要进行数据转换,但这只是问题的表面。问题的根本在于两个系统所使用的模型是不同的。
注意如果发生在同一个系统中,则更难发现。
模型混乱的根源在于团队的组织方式和成员的交流方法,因此,为了澄清模型的上下文,既要注意项目,也要注意它的最终产品(代码,数据库模式)
一个模型只在一个上下文中使用,这个上下文可以是代码的一个特定部分,也可以是某个特定团队的工作。
模型上下文是为了保证该模型中的术语具有特定意义而必须要应用的一组条件。
为了解决多个模型的问题,需要明确定义模型的范围---模型的范围是软件系统中一个有界的部分,这部分只应用一个模型,并尽可能使其保持统一。
团队组织中必须一致遵守这个定义。
明确地定义模型所应用的上下文,根据团队的组织,软件系统的各个部分的用法以及物理表现(代码和数据库模式)来设置模型的边界。
在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
context map 画出了上下文的范围,并给出了 Context 以及它们之间联系的总体视图,
而几种模式定义了Context之间的各种关系的性质。
Continuous integration 的过程可以使模型在 Bounded Context 中保持统一。
识别 Bounded Context 中的不一致
1. 最明显的是已编码的接口不匹配
2. 一些意外情况也可能是一种信号。 自动测试的 continuouse integration 可以帮助捕捉到这类问题。
3. 语言上的混乱。
将不同模型的元素组合到一起可能会引发两类问题:
1.重复的概念: 两个模型元素(以及伴随的实现) 实际上是同一个概念,每当这个概念的信息发生变化时,都必须更新两个地方。
每次由于新知识导致一个对象被修改时,必须重新分析和修改另一个对象。遵守不同的规则,甚至有不同的数据,更严重的是团队成员必须
学习做同一件事情的两种方法,以及保持这两种方法的同步的各种方式。
2.假同源:使用相同的术语或已实现的对象的两个人认为是在讨论同一件事情,实际并不是。
假同源会导致开发团队互相干扰对方的代码,也可能导致数据库中含有奇怪的矛盾,引起团队沟通的混淆。
模式1 Continuous integraion
极限编程XP 针对很多人频繁更改设计的情况下如何维护设计的一致性这个特定问题而出现的,
维护单一 Bounded Context 中的模型完整性。
Continuouse integraion 是指把一个上下文中的所有工作足够频繁地合并在一起,并使它们保持一致,
以便当模型发生分裂时,可以迅速发现并纠正问题。
两个级别的操作: 1)模型概念的集成 。 2)实现的集成
团队成员通过经常沟通来保证概念的集成。团队必须对不断变化的模型形成一个共同的理解。最基本的方法是对Ubiquitous language 多加锤炼。
实际工件通过系统性的合并/构建/测试过程来集成,这样能够尽早暴露出模型的分裂问题。
大部分有效的过程具备以下特征:
1.分步集成, 采用可重现的合并/构建技术。
2.自动测试套件。
3.有一些规则,用来为那些尚未集成的改动设置一个相当小的生命期上限。
概念集成
讨论模型和应用程序时要坚持使用 Ubiqutious language
建立一个把所有代码和其他实现工件频繁地合并到一起的过程,并通过自动化测试来快速查明模型的分裂问题。
严格坚持使用 Ubiquitous language。
Continous integration 只有在Bounded context 中才是最重要的。维护单一模型的完整性.
2021-02-03
Bound context
将两个模型关联起来的策略,把模型连接到一起之后,就能把整个企业笼括在内。
这些模式有双重的目的:
1. 为成功的组织开发工作设定目标。
2. 为描述现有组织提供术语。
这些模式的主要区别包括你对另一个模型的控制程度,两个团队之间的合作水平和合作类型,以及特性和数据的集成程度。
团队需要为不同的用户群提供服务,或者团队的协调能力有限,可能就需要采用共享内核 Shared kernel 或 Customer / Supplier 客户/供应商 关系。
发现集成并不重要,而系统最好采用 Separate way 各行其道模式,
大多数项目需要与遗留系统或外部系统进行一定程度的集成,这就需要使用 Open host service 开发主机服务 或 AntiCorruption Layer 防护层。
shared kernel
从领域模型中选出两个团队都同意共享的一个子集。
除了这个模型子集之外,还包括与该模型部分相关的代码子集,或数据库设计的子集.
功能系统要经常进行集成。
共享内核中必须集成 自动测试套件,因为修改共享内核时 ,必须要通过两个团队的所有测试。
Shared kernel 通常是 Core Domain, 或是一组 Generic Subdomain 通用子领域。可能二者兼有,
可以是两个两个团队都需要的任何一部分模型。
使用 Shared Kernel 的目的是减少重复,并不是消除重复,只有在一个 Bounded Context 中才能消除重复,
使得两个子系统之间的集成变得相对容易一些。
Customer / Supplier development team
一个子系统主要服务于另一个子系统;“下游”组件执行分析或其他功能,这些功能向“上游” 组件反馈的信息非常少,所有的依赖都是单向的。
两个子系统通常服务 于完全不同的用户群,其执行的任务 不同,
这种情况下使用 不同的模型会很有帮助,工具集可能也不相同,因此无法共享程序代码。
迭代计划过程。
两个团队共同开发自动化验收测试,用来验证预期的接口。
把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。
这些测试使上游团队在做出修改时不必担心对下游团队产生副作用。
上游团队把这些自动化验收测试作为标准测试套件的一部分来运行。修改测试就意味着修改接口。
使用了不同实现技术时,Shared kernel 是很难做到的。
Conformist 跟随者模式 Anticorruption Layer
新的系统与遗留系统或其他系统进行集成,这些系统具有自己的模型。
把参与集成的Bounded Context 设计完善且团队相互合作时,转换层可能很简单,甚至很优雅。
边界那侧发生渗透时,转换层就要承担更多的防护职责。
从一个系统中取出一些数据,然后在另一个系统中错误地解释了它,显然会发生错误,甚至会破坏数据库。
数据与每个系统的关联方式会使数据的含义出现细微但重要的差别。
即使原始数据元素确实具有完全相同的含义,但在原始数据这样低的层次上进行接口操作通常是错误的。
这样的 底层接口使另一个系统的模型丧失了解释数据以及约束其值和关系的能力,同时使新系统背负了解释原始数据的负担,
而且并未使用这些数据自己的模型。
需要在不同模型的关联部分之间建立转换机制,这样模型就不会被未经消化的外来模型元素所破坏。
创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。
这个层通过另一个系统现有接口与其进行对话,而只需对那个系统做出很少的修改,甚至无需修改。
在内部,这个层在两个模型之间进行必要的双向转换。
AntiCorruption Layer 并不是向另一个系统发送消息的机制。相反,它是在
不同的模型和协议之间转换概念对象和操作的机制。
设计AntiCorruption Layer 的接口
AntiCorruption layer 的公共接口通常以一组Service 的形式出现,偶尔也会采用Entity的形式.
构建一个全新的层来负责两个系统之间的语义转换为我们提供了一个机会,它使我们能够重新对另一个系统的行为进行抽象,
并按照与我们的模型一致的方式把服务和信息提供给我们的系统。
在我们的模型中,把外部系统表示为一个单独的组件可能使没有意义的。
最好是使用多个Service,其中每个Service都是用我们的模型来履行一致的职责。
对 AntiCorruption layer 设计进行组织的一种方法是把它实现为 Facde, Adapter 和 转换器的组合,
外加两个系统之间进行对话所需的通信和传输机制。
概念模型的差别是我们使用 AntiCorruption Layer 的动机。
与那些具有大而复杂,混乱的接口的系统进行集成, 实现问题。
场景:从一个模型转化到另一个模型的时候,如果不能同时处理那些难于沟通的子系统接口,那么将很难完成。Facde 解决。
Facade 是子系统的一个可供替换的接口,它简化了客户访问,并使子系统更易使用。
由于我们非常清楚要使用另一个系统的哪些功能,因此可以创建Facade来促进和简化对这些特性的访问,并把其他特性隐藏起来。
Facade 并不改变底层系统的模型,应该严格按照另一个系统的模型来编写。
否则会产生严重的后果:轻则导致转换职责蔓延到多个对象中,并加重Facade的负担。重则创建出另一个模型,谁也不属于。
Facade 应该属于另一个系统的 boudned context, 它只是为了满足你的专门需要而呈现出的一个更友好的外观。
Adapter 是一个包装器,允许客户使用另外一种协议,这种协议可以是行为实现者所不理解的协议。
当客户向适配器发送一条消息时,Adapter 把消息转换为一条语义上等同的消息,并将其发送给 被适配者 adaptee,
之后Adapter 对响应消息进行转换,并将其发回。
GoF 强调的时使包装后的对象符合客户所期望的接口,而我们选择的使被适配的接口,而且被适配者甚至可能不是一个对象。
我们强调的是两个模型之间的转换。
所定义的每种Service 都需要一个支持其接口的Adapter,这个适配器还需要知道怎样才能向其他系统及其Facade发出相应的请求.
Adapter 的工作是知道如何生成请求。
概念对象或数据的实际转换是一种完全不同的复杂任务。可以让一个单独的对象来承担这项任务,可以使负责转换的对象和 Adapter
都更易于 理解。转换器可以是一个轻量级的对象,可以在需要的时候被实例化。
由于它只属于它所服务的Adapter, 因此不需要有状态,也不需要是分布式的.
连接两个子系统的通信机制,可能位于不同的服务器上。
必须决定在哪里防止通信链接.
如果无法访问另一个子系统,那么可能必须在Facade和另一个子系统之间设置通信链接。
如果Facade可以直接与另一个子系统集成到一起,那么在适配器和Facade之间设计通信链接也不失为一种好的选择,因为Facade的协议比它所封装的内容要简单。
有些情况下,整个AntiCorrutpion Layer可以与另一个子系统放在一起,这时可以在你的系统和构成AntiCorruption layer接口的Service之间设置通信
链接或分发机制。
有权访问另一个系统时,考虑少许重构,为那些需要使用的功能编写更显式的接口。如果可能的化,首先从编写自动测试开始。
需要广泛的集成时,转换的成本会直线上升,对正在设计的系统的模型做出一些选择,使之尽量接近外部系统,以便使转换更容易。
如果另一个子系统很简单或有一个很整洁的接口,可以不需要Facade.
如果一个功能是两个系统的关系所需,把这个功能添加到AntiCorruption layer, 两个特性:1 外部系统使用情况的审计跟踪,
2 跟踪逻辑,其用于对另一个接口的调用。
2021-02-06
选择核心
技术能力最强的人员往往缺乏丰富的领域知识。
方法使建立一支由开发人员和一位和多位领域专家组成的联合团队,其中开发人员必须能力很强,能够长期稳定地工作并且对学习领域知识非常感兴趣,
而领域专家则要掌握深厚的业务知识,认真对待领域设计,一项有趣且充满技术挑战的工作。
精炼
1.简单的 domain vision statement 领域愿景说明,highlighted core 突出核心。
2.通过重构和重新打包显式地分离出Generic Subdomain, 然后单独进行处理. 在使用 Conhesive Mechanism 的同时,也要保持设计的通用性,易懂性和柔性,
这两个方面可以结合起来,只有除去了这些细枝末节,才能把core 剥离出来。
3 重新打包出一个 Segregated core 分离的核心,可以使这个 Core 清晰可见,且促进将来的Core模型上的工作。
4.最富雄心的精炼是 Abstract core 抽象内核,用纯粹的形式表示了最基本的概念和关系,需要对模型进行全面组织和 重构。
精炼
精炼的价值在于使你能够看到自己正在做什么。
如果领域中哪些起到支持作用的部分提供了一种简练的语言,可用表示core的概念和规则,同时又能够把计算或实施
这些概念和规则的方式封装起来,那么Core Domain的重要部分就可以采用声明式设计。
Conhensive Mechanism 用途最大的地方是它通过一个Intention-Revealing interface 来提供访问,并且具有概念上一致
的Assertion 和 Side-effect-free function.
利用这些Mechanism 和 柔性设计, Core domain 可使用有意义的声明,而不必调用难懂的函数。
但最不同寻常的回报来自于使 Core domain 的一部分产生突破,得到一个深层模型,而且这部分核心领域本身成为了一种语言,
可以灵活且精确地表达出最重要的应用场景。
深层模型往往与相对应的柔性设计一起产生。柔性设计变得成熟的时候,就可以提供一组易于理解的元素,
我们可以明确地把他们组合到一起来完成复杂的任务,或表达复杂的信息,就像单词组成句子一样。
客户代码就可以采用声明式风格,而且更为精炼。
Segregated core
模型中的元素可能有一部分属于 Core Domain, 而另一部分起支持作用。
核心元素可能与一般元素耦合在一起。
Core的概念内聚性可能不是很强,这种混乱性和耦合关系抑制了 Core。
设计人员如果无法清晰地看到最重要的关系,就会开发出脆弱的设计。
对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来,并增强Core的内聚性,
同时减少它与其他代码的耦合。
把所有通用性元素或支持元素提取到其他对象中,并把这些对象放到其他包中--即使这会把一些紧密耦合的元素分开。
Abstract core
处理大模型的方法通常是分解为足够小的子领域,以便能够掌握它们并把它们放到一些独立的module中。
简单的打包风格通常是行之有效的,能够使一个复杂的模型变得易于管理。
但有时创建独立的Module反而会使子领域之间的交互变得晦涩难懂。
当不同的Module的子领域之间有大量交互时,要么需要在Module之间创建很多引用,这在很大程度上抵消了划分模块的价值;
要么就必须间接地实现这些交互,而后者会使模型晦涩难懂。
采用横向切割而不是纵向切割的方式。
多态性 polymorphism 允许我们忽略抽象类型实例的很多细节变化。
如果 Module 之间的大部分交互都可以在多态接口 这个层次上表达出来,那么就可以把这些类型重构到一个特定的 Core Module 中。
只有当领域中的基本概念能够用多态接口来表达时,这才是一种有价值的技术。
把模型中最基本的概念识别出来,并分离到不同的类,抽象类或接口中。
设计这个抽象模型使之能够表达出重要组件之间的大部分交互。
把这个完整的抽象模型放到它自己的module中,
而专用的,详细的实现类则留在由子领域定义的module中。
精炼文档
大型结构
团队规模较小且模型不太复杂时,只需将模型分解为合理命名的Module, 再进行一定程度的精炼,然后在开发人员之间进行非正式的协调,
以上就足以使模型保持良好的组织结构。
1. System metaphor 系统隐喻
2. responsibility layer
单独的对象被分配给了一组相关的,范围较窄的职责,指责驱动的设计在更大的规模上也适用。
当对领域有了深入的理解后,大的模式就会变得清晰起来。
一些领域具有自然的层次结构。
某些概念和活动处在其他元素形成的一个大背景下,而那些元素会因不同原因且以不同频率独立发生变化。
自然的层次结构-》 领域分层,最成功的架构设计模式之一.
对模型的理解并没有多大的帮助,也不能指导建模决策。
在一个具有自然层次结构的模型中,可以围绕主要职责进行概念上的分层,可以把分层和职责驱动的设计这两个强有力的
原则结合起来适用。
当设计单独的module和Aggregate时,要将其限定在其中一个主要职责上。
这种明确的职责分组可以提高模块化系统的可理解性,因为Module的职责会变得更易于理解。
而高层此的职责与分层的结合为我们提供了一种系统的组织原则。
松散分层系统最适合 按职责来分层。
观察模型中的概念依赖性,以及领域中不同部分的变化频率 和 变化的原因。
如果在领域中发现了自然的层次结构,就把它们转换为宽泛抽象职责。
这些职责应该描述系统的高层目的和设计。对模型进行重构,使得每个领域对象,
Aggregate 和 Module 的职责都清晰地位于一个职责层中。
生产控制 和 财务管理, 适用 Responsibility Layer 职责层所产生的良好效果。
2021-02-08
责任驱动设计 p312
1. "作业职责“
2. “能力职责”
由于双向关联会破坏分层,因此用查询来代替它。
这两个层主要考虑的是当前的情况或计划,但 Router (及其他因素)并不是当前的作业或计划的一部分。
它是用来帮助修改这些计划的。负责决策支持-》
3. 决策支持 职责层
Layer 层的设计 p318
要找到一种适当的 Responsibility layer 或 大比例结构,需要理解问题领域并反复进行实验。 遵循 Evolving Order, 最初的起点并不十分重要。
指导方针,无论是刚开始选择一种结构,还是对已有结构进行转换,这些指导方针都适用。
当对层进行删除,合并,拆分和重新定义 等操作,应寻找保留以下一些有用的特征。
1. 场景描述。 层应该能够表达出领域的基本现实或优先级。选择一种大比例结构与其说是一种技术决策,不如说是一种业务建模决策。层应该显示出业务的优先级。
2. 概念依赖性。“较高”层概念的意义依赖于“较低”的层,而“底层”概念的意义应该独立于较高的层。
3. conceptual contour. 如果不同层的对象必须具有不同的变化频率或原因,那么层应该容许它们之间的变化。
潜能层:
能够做什么,潜能层不关心打算做什么,而关心能够做什么。企业的资源(包括人力资源)以及这些资源的组织方式是潜能层的核心.
该层几乎存在任何业务领域中,在那些依靠大型固定资产来支持业务运作的企业中(如运输和制造业)尤其突出。
潜能也包括临时性的资产,但主要依靠临时性资产来运作的企业可能会强调临时资产的层(被称为“capability")
作业层:
正在做什么,利用这些潜能做了什么事情?像潜能层一样,应该反映出现实状况,而不是设想的状况。
在这个层中看到自己的工作和活动:正在销售什么,而不是能够销售什么。
作业层可以引用潜能层,甚至可以由潜能层对象组成,但潜能层对象不应该引用作业层对象。
这俩层可以跟踪当前状况 和 正在执行的作业计划,以及问题报告或相关文档。
但跟踪往往是不够的。当项目要为用户提供指导或帮助或者要自动制定一些决策时,就需要有另外一组职责,
这些职责可以被组织到作业层之上的 决策支持 层中。
决策支持层:
有对其他层(如潜能层和作业层)概念上的依赖。决策不是凭空指定的。
很多项目都利用数据仓库技术来实现决策支持。实际上变成了一个独特的 Bounded Context,且与作业软件具有一种Customer/Supplier关系。
分层结构一个内在的优点时较低的层可以独立于较高的层存在.
这样有利于在较老的作业系统上分阶段引入新功能或开发高层次的增强功能 .
策略层
软件实施了详细的业务规则 或 法律需求,这些规则或需求可以形成一个 Responsibility Layer
规则 和目标是什么? 二者是被动的,但约束着其他层 的行为。
有时会使用参数的形式传递给较低层的方法,有时会使用 strategy 模式。
决策支持层提供了用于搜索 策略层所设定的目标 的方式.
这些目标又受到 策略层所设定的规则 的约束
可以使用与其他层同一种语言 ,也可以是规则引擎来实现。并不是说一定要把它们放到一个单独的 Bounded Context 中。
通过在两种不同实现技术中严格使用 同一个模型,较少在两种实现技术之间协调的难度。
金融服务或保险业中,潜能很大程度上是由当前的运营状况决定的。
一家保险公司在考虑签保单承担 理赔责任时,要根据当前业务的多样性来判断是否又能力承担它所带来的风险。
潜能层被合并到作业层中-》 演变出 承诺层
承诺层
承诺了什么,具有策略层的性质,
表述了一种指导未来运营的目标,
也有作业层的性质,因为承诺是作为后续业务活动的一部分而出现和变化的。
潜能层和承诺层不互相排斥,例如提供很多定制运输服务的运输公司 ,这两个层 都很重要,可以同时使用。
要使分层系统保持简单,层数如果超过4或5,就会难处理,层数过多无法有效地描述领域,而且本来要使用
大比例结构解决 的复杂性问题又会以一种新的方式出现。
模式 Knowledge level
当我们需要让用户对模型的一部分有所控制,而模型又必须满足更大的一组规则时,可以利用 Knowledge level 来处理这种情况。
可以使软件具有可配置的行为,其中实体中的角色和关系必须在安装时(甚至在运行时)进行修改。
知识级别的这种模式是讨论在组织内部对责任进行建模的时候提到的。
Knowledge level 并不像其他分析模式那样对领域进行建模,而是用来构造模型的.
为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性.
具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则.
在我们的模型中嵌入了另一个模型,而它的作用只是描述了我们的模型。
Knowledge level 分离了模型的这个自我定义的方面,并清楚地显示了它的限制.
Knowledge level 是 Reflection 模式在领域层中的一种应用,很多软件架构和技术基础设施中都使用了它,
Reflection 模式能够使软件具有“自我感知” 的特性,并使所选中的结构和行为可以接受调整和修改,从而满足变化的需要。
实现:
通过将软件分为两个层来实现的,
一个层是“基础级别 base level" , 它承担应用程序的操作职责;
另一个是”元级别 meta level", 表示有关软件结构和行为方面的知识.
我们并没有把这种模式叫做 知识“层” layer , 虽然 reflection 与分层很类似,但反射却包含双向依赖关系.
java 一些最基本的内置 reflection 机制,采用的是协议的形式,用于查询一个类的方法等。
一些持久化技术增加了更丰富的自描述特征,在数据表和对象之间提供了部分自动化映射。
Knowledge level 必须使用普通对象来构造.
编程语言的反射工具并不是用于实现领域模型的 Knowledge level 的,这些元对象描述的 是语言构造本身的结构 和行为。
两个很有用的特性:
1. 它关注的是应用领域,这一点与人们所熟悉的Reflection模式的应用正好相反。
2.它并不追求完全的通用性。
一个 Specification 可能比通用的断言更有用一样,专门为一组对象和它们的关系的定制的一个约束集可能比一个通用的框架更有用。
创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。
把这些对象分为两个"级别”
1. 非常具体的级别。
2. 提供了一些可供用户或超级用户定制的规则和知识
数据迁移的基本问题并没有完全得到解决。
当 Knowledge level 中的某个结构发生变化时,必须对现有的操作级别中的对象进行相应的处理。
新旧对象确实可以共存,需要仔细的分析。
Knowledge level 的设计人员增加了沉重的负担。
设计必须足够健壮,因为不仅要解决开发中可能出现的各种问题,而且还要考虑到将来用户在配置软件时可能出现的各种问题。
如果得到合理的运用,Knowledge level 能够解决一些其他方式很难解决的问题。
如果系统中某些部分的定制非常关键. 而要是不提供定制能力就会破坏掉整个设计,这是就可以利用知识级别来解决这一问题.
特有的访问约束和一种 “事物-事物” 型的关系对开发团队起到了提示作用,看到了隐含的 Knowledge level.
一旦 Knowledge level 被分离出来,它就能够使模型变得非常清晰,从而可以提取出 payroll 将两个重要的领域概念分开。
Pluggable component framework
abstract core p329
2021-02-09 23:01:30
结构应该有的约束
responsibility layer 规定了一种用于划分模型概念以及它们的依赖性的方式,但我们也可以添加一些规则,来指定各个层之间的通信模式.
低层往高层信息
通常这种信号传递是通过某种事件机制实现的,每当作业层对象的状态发生改变时,它们就将生成事件。
策略层对象将监听来自较低层的相关事件.
如果一个事件违反了某个规则,该规则将执行一个动作(规则定义的一部分)来给出适当的响应,或者生成一个事件反馈给更高的层,以便
帮助更高的层做出决策。
可以为不同的情况设计不同的事件机制,也可以让特殊层中的对象在交互时遵守一种一致的模式。
结构越严格,一致性就越高,设计就越容易理解。
如果结构适当的化,规则将推动开发人员得出好的设计。不同的部分之间会更协调。
约束也会限制开发人员所需的灵活性。
大比例结构的最重要贡献在于它具有概念上的一致性,并帮助我们更深入地理解领域。 p335
1. 最小化
控制成本的一个关键时保持一种简单,轻量级的结构。
如 System metaphor 或几个 Responsibility layer
2. 沟通和自律。
在新的开发和重构中必须遵守结构。
3. 通过重构得到柔性设计
4.通过精炼可以减轻负担。
2021-02-10
领域驱动设计的综合运用
战略设计的三个基本原则(上下文,精炼,和大型结构) 并不是可以互相代替的,而是互为补充,且以多种方式进行互动.
responsibility layer 的例子被限定在一个 Bounded context 中,这是解释这一思想的最简单方法,也是该模式的一般用法。
在这样简单的场景中, 层名称的含义仅用于该 Context, 该Context中的模型元素或子系统接口的名称也是如此.
这样的局部结构在一个非常复杂但统一的模型中是很有用的,它使系统所能承受的复杂度上限提高了,进而使得在一个
Bounded context 中可以维护更多的对象.
更大的挑战是理解怎样使各个不同的部分构成一个整体,这些部分可能被划分到不同的Bounded context .
但是各个部分在整个集成系统中的作用是什么,它们之间又是如何互相关联的?
理解了这些问题之后就可以用大型结构来组织 Context Map .
结构的术语适用于整个项目,或至少是项目中某个明确限定的部分。
responsibility layer 模式,有一个遗留系统,它的组织结构与你想要采用的大型结构不一致.
必须确定遗留系统在新结构中的位置
遗留系统所提供的 Service 可以被限定到几个层中. 如果我们能够说出遗留系统与哪几个特定的 Responsibility layer 相符。
就非常精确地描述了遗留系统的范围和角色的关键方面.
如果遗留子系统的功能是通过一个Facade 来访问的,设计时,该Facade所提供的每个 Service应该只在一个层中,不跨越多个层.
对一个项目进行战略设计时,首先要清晰地评估现状
1) 画出 Context Map, 画出一个一致的图,有没有一些模棱两可的情况?
2)注意项目上语言的使用,有没有 Ubiquitous language ? 这种语言是否足够丰富,以便帮助开发?
3)理解重点所在。 Core domain 被识别出来了吗?有没有 domain vision statement ? 能写一个。
4)项目所采用的技术是遵循 Model-driven design, 还是与之相悖?
5)团队开发人员是否具备必要的技能?
6)开发人员是否了解领域知识?他们对领域是否感兴趣?
精炼的答案:特别是 Context Map, Domain vision statement, 以及其他创建出来的工件,反映出了变化的情况和新的理解。
制定战略设计决策的6个要点
1. 决策必须传达到整个团队
要关注开发人员与策略之间的实际关系。
2. 决策过程必须收集反馈意见
与基础设施和架构不同,战略设计虽然影响到所有开发工作,但是它本身并不需要编写很多代码,需要的是应用开发团队的参与。
3. 设计必须允许演变
所有应用程序团队都应该有一些技术能力很强的设计人员,而且任何从事战略设计的团队也都必须具有领域知识。两者都非常重要。
4.战略设计需要遵守简约和谦逊的原则
必须严格地约束自己,从而使设计出来的组织原则和核心模型精简到只包含那些能够显著提高设计清晰度的内容。
5. 对象的职责要单一, 而开发人员应该使多面手
良好的对象的设计的关键是为每个对象分配一个明确且专一的职责,并且把对象之间的依赖减至最小.
虽然基于深层模型创建柔性设计是一种高级设计活动,但细节问题也至关重要,因此战略设计的工作必须由接触编码工作的人来完成。
战略设计源自应用设计,然而战略设计需要一个总体的开发活动视图,这个视图可能跨越多个团队。
过于专业化也会削弱领域驱动设计的力量。
技术框架提供了基础设施层,帮助把领域和其他关注点隔离开,因此能够极大地加速应用程序(包括领域层)的开发。
技术框架也是有风险的,它会影响领域模型实现的表达能力,并妨碍领域模型的自由改变。
注意总体规划
聚少成多地成长 piecemeal growth.
2021-02-11 00:19:50

浙公网安备 33010602011771号