8/16开始,10/16结束检视阅读。惭愧,其实不算检视阅读,还有点分析阅读了,总觉得自己阅读方法把握还是不够,不能充分有效地利用时间,我想,我完全按照检视阅读的要求去看一本技术书,最后能得到多少知识呢?还是值得一试的,看看自己的器量。
Eric Evans
domain-driven design
借鉴 Christopher Alexander 的建筑设计模式语言模式组织本书,同设计模式作者一样从他身上得到了影响。
本以为才开始,没想到是巅峰
聚焦于软件的核心领域,以它来驱动开发。核心领域是核心竞争力,是利润所在的地方,也是最值得下功夫的地方,再难也不能逃避。
领域模型并不是按照先建模,后实现来工作的,一般是在系统初始版本完成之后迭代,才有最好的想法的。
Eric清楚地表明他像我们大多数人一样,既品尝过成功的美酒,也体验过失败的沮丧。重要的是他能够从成功和失败中学习。
第一遍读后感
总的来说这本书的思想是想要提供一种像建筑学那样通用的模式设计语言来促使软件的构建更加地灵活,易懂且有效。跟设计模式相同点就是提供一种模式思路和通用语言想法,不同点是设计模式主要用于解决技术问题,而领域驱动设计更偏向于让我们与领域专家一起为所要构建的业务领域知识设计一个更好的业务知识模型。前面的理论大体上是看得懂了,但后面几章中的模式方法因为纯理论没有深入实践理解,脑子浮躁,所以根本弄不清楚到底是怎么回事,怎么使用,也因此,到最后两章直接放弃不看了。虽然不看了,但这只是第一遍,还会有第二,第三遍的,所以继往开来,有个总体的印象和知识,这个目的是达到了的,等后面自己要设计一个系统时,再来参考实践这些方法,边学变练。
纸上得来终觉浅,绝知此事要躬行。
前言
跟设计模式一样,领域设计也有一套技术词汇库,方便设计开发者之间交流沟通。
领域驱动设计的效果目标是——交付后能够满足组织后续需求,不断演进扩展。扩展性
误区:仅靠使用模型并不会是项目达到富有扩展性这样的良性循环。注意建模(设计人员)与实现(开发人员)不能相脱节。这样会导致设计无法反映不断深化的分析。
领域驱动设计对应的开发过程为——敏捷开发过程。
迭代开发。
开发人员与领域庄佳具有密切的关系。
极限编程最适合哪些对设计的感觉很敏锐的开发人员。
学习目标:学会用高级建模和设计技巧来解决实际问题。
核心章节:引言、1、2、3、9、14
第三和第四部分很有必要重新研读,里面都是写模式方法的使用和举例。
阅读本书的方式:已掌握的知识可以采取跳跃式阅读的方式,通过阅读标题和粗体字内容掌握要点。高级读者可以跳过前两部分,重点阅读第三、四部分内容。(这两部分是方法论实践)
团队成员共同应用领域驱动设计方法就有了一种公共语言,可以进行更充分的沟通,创建出一个与模型步调一致的实现。
p17 图形关系
一、让领域模式发挥作用
三个基本用途决定了模型的选择。
软件的核心是其为用户解决领域相关的问题的能力。
理解领域驱动设计核心目的的故事。
开发人员可以采用一些系统性的思考方法来透彻的理解领域并开发出有效的模型。还有一些设计技术可以使毫无头绪的软件应用程序开发工作变得井井有条。掌握这些技巧可以令开发人员身价倍增,即使是在一个最初不熟悉的领域中也是如此。(即使是在一个最初不熟悉的领域中也是如此?)
消化业务知识
有效建模的要素:
模型和实现的绑定。
获得了一种基于模型的语言。(方便沟通)
开发一个蕴含丰富知识的模型。
提炼模型。
头脑风暴和实验。
领域模型的不断精化迫使开发人员学习重要的业务原理(深入理解业务知识和思想),而不是机械地进行功能的开发。领域专家被迫提炼自己所知道的重要知识的过程往往也是完善其自身理解的过程,而且他们会渐渐理解软件项目锁必需的概念严谨性。
无知往往会导致我们做出错误的假设。——对领域知识在技术上难度的轻视。
高效率的团队需要有意识地积累知识,并持续学习。对于开发人员来说,这意味着既要完善技术知识,也要培养一般的领域建模技巧(以及目前正在从事的特定领域的知识)。善于自学的团队成员是团队的中坚力量。
例子:通过策略模式将航运领域的超订规则明确展示出来表示它是一个政策。
要想建立实用且清晰的模型要求团队成员既要精通领域知识,也要精通建模技术。
例子:航运业务的认识从“集装箱在各个地点之间的运输”转变为“运货责任在各个实体之间的传递”。
疑问
系统性的思考方法怎么理解?
即使是在一个最初不熟悉的领域中也是如此?
头脑风暴?
语言的交流和使用
UML
没有公共语言的项目交流就像没有翻译的两个说不同国家语言的人在聊天。知识消化理解变得困难。
领域模型的模式名称。
将模型作为语言的中心。确保交流、画图、代码(注意命名描述)中使用这种语言。
领域专家应该避免使用拗口或无法表达领域理解的术语或结构,开发人员应该密切监视那些将会妨碍设计的有歧义和不一致的地方。
P41领域驱动交流对话
用简单高效易懂的语言交流来形成模型。
一个团队,一种语言
如果连经验丰富的领域专家都不能理解模型,那么模型一定是有问题的。
UML擅长表达对象的关系和交互。
UML无法传达模型的两个最重要的方面:
模型所表示的概念的意义。
对象应该做哪些事情。
模型设计的重要细节应该在代码中体现出来。以文本或描述为主,简略图为注释?
模型不是图。图的目的是帮助表达和解释模型。(不能强制完全用图来表示模型或设计,因为这样会削弱图的清晰表达的能力)
文档的作用:
文档应作为代码和口头交流的补充。完全依赖代码作为一种交流媒介可以促使开发人员保持代码的整洁和透明。
注意代码作为设计文档的局限性:会把读代码的人淹没在细节中。因此文档应澄清设计意图,着重说明含义,不应重复表示代码已经明确表达的内容。
文档应努力心情生存之道并保持最新。通过将文档减至最少,并且注意用它来补充代码和口头交流,就可以避免文档与项目脱节。
P50解释性模型展示
疑问
将模型作为语言的中心,什么意思,什么叫模型?可以说是UML图加大家约定成俗的语言描述么?
绑定模型和实现
领域驱动设计要求模型不仅能够指导早期的分析工作,还应该成为设计的基础。这种设计方法对于代码的编写有着重要的暗示作用。
如果整个程序设计或者其核心部分没有与领域模型相对应,那么这个模型就是没有价值的,软件的正确性也值得怀疑。同时,模型和设计功能之间太过复杂的对应关系也是难于理解的,在实际项目中改变设计时也无法维护这种关系。分析和设计工作无关联,导致在这两个过程中所获得的知识无法彼此共享。
软件系统各个部分的设计应该忠实地反映领域模型,以便体现出这两者之间的明确对应关系。我们应该繁复检查并修改模型,在软件中更加自然地实现模型,即使想让它反映出更深层次的领域概念也应如此。我们需要的模型不但应该满足这两种需求,还应该能够支持健壮的通用语言。
从模型中获取用于程序设计和基本任务分配的术语。程序代码就是模型的表达,修改代码可以就是改变模型。
想要使代码有效地描述模型就需要用到程序设计和实现的技巧(第二部分)
软件开发是一个不断精化模型、设计和代码的统一的迭代过程(第三部分)
对象设计的真正突破是用代码来描述模型中的概念。
用过程语言(C)编写的软件具有复杂的函数,这些函数基于预先制定的执行路径连接在一起,而不是通过领域模型中的概念联系连接的。
例子:P56 从过程设计到模型驱动设计
模型驱动设计中要结合设计模式来实现。
让用户更了解模型,给他们更多的集合挖掘软件的潜能,也能使软件的行为合乎情理、前后一致。
模式:Hands-on modeler (建模人员参与程序开发)
经验丰富的工程师做设计工作,而技能水平较低的劳动力负责组装产品。
架构师不能脱离编程而设计架构,因为将分析、建模、设计和编程工作完全分离会对Model-driven design 产生不良影响。
如果项目组的分工阻断了设计人员与开发人员之间的协作,使他们无法领悟Model-driven design 的奥秘,那么经验丰富的设计人员就不能将自己的知识和技术传递给开发人员。
任何参与建模的技术人员,不管在项目中的主要职责是什么,都必须花时间了解代码。任何负责修改代码的人员则必须学会用代码来表达模型。每一个开发人员都必须不同程度地参与模型讨论并且与领域专家保持联系。参与不同工作的人都必须有意识地通过Ubiquitous Language 与接触代码的人即使交换关于模型的想法。(这一段描述让我想起了在亚信做订单中心时的场景,虽然中间模型几次调整讨论,但总的来说是符合模型驱动设计的理念的,达到了充分交流设计,模型驱动设计的成功离不开详尽的设计决策。)
疑问
设计和模型相脱离后怎么办?
二、模式驱动设计的构造块
开发一个好的领域模型是一门艺术。而模型中各个元素的实际设计和实现则相对系统化。将领域设计与软件系统中的其他关注点分离会使设计与模型之间的关系非常清晰。根据不同的特征来定义模型元素则会使元素的意义更加鲜明。对每个元素使用已验证的模式有助于创建更易于实现的模型。
疑问
设计风格中,什么叫职责驱动设计的原则?
”契约式设计“思想是指?
分离领域
Layered Architecture (分层架构)
分层架构的基本原则:
层中的任何元素都仅依赖于本层的其他元素或其下层的元素。向上的通信必须通过间接的传递机制进行(如回调模式或观察者模式)。
一般的四个分层:
表示层(controller)
应用层(service逻辑层)
领域层(模型层,对象、结构)
基础设施层(dao等)
使用框架的一些缺点:要么是设定了太多的假设,减小了领域设计的可选范围;要么是需要实现太多的东西,影响开发进度。
将领域实现独立出来是领域驱动设计的前提。所以分层架构是必须的。
要想创建出能够处理复杂任务的程序,需要把不同的关注点分开考虑,使设计中的每个部分都得到单独的关注。在分离的同时,也需要维护系统内部复杂的交互关系。
模式:The Smart UI "Anti-pattern"(智能用户界面——反模式)用于与领域驱动设计作对比
一次性简单开发才可能用到,已废
把领域隔离出来的最大好处就是可以真正专注于领域设计,而不用考虑其他的方面。
疑问
为什么说J2EE中的实体bean比较笨重,影响程序性能,而改用spring的普通java对象比较轻呢?
软件中所表示的模型
区分用于表示模型元素的三种模式:Entity、 Value Object、 Service
Entity(实体、标识):一个对象是用来表示某种具有连续性和标识的事物。
Value Object(值对象):用于描述某个事物的某种状态的属性。
Service(服务) :领域中适合用动作或操作来表示的方面。
使得对象关联更易于控制的三个方法:
规定一个遍历方向。
添加一个限定符,以便有效地减少多重关联。
消除不必要的关联。
坚持将关联限定为领域中所偏向的方向,不仅可以提高这些关联的表达力并简化其实现,而且还可以突出剩下的双向关联的重要性。
关联关系最终的简化是消除那些对当前工作或模型对象的基本含义来说不重要的关联。
仔细地简化和约束模型的关联是通往模式驱动设计的必经之路。
模式:Entity(又称为Reference Object)引用对象
Entity可以是任何事物,只要满足两个条件即可:
它在整个生命周期中具有连续性。
它的一些对用户来说非常重要的不同状态不是由属性决定的。(怎么理解?)
标识重不重要,完全取决于它是否有用。同一个事物在领域模型中可能需要表示为Entity,也可能不需要表示为Entity。
例子:演唱会座位预定程序中的座位。如果分配座位时是一对一对号入座,则需要座位是个标识Entity,而座位号就是其标识符;而如果分配座位时是先到先做的话,则不需要座位是个标识Entity加以区分,只有座位总数才是重要的。
Entity最基本职责是确保连续性,以便使其行为更清楚且可预测。保持实体的简练是实现这一责任的关键。
Entity的唯一ID可以通过技术框架实现,也可以通过工程规程来约束。
模式: Value Object (值对象)
用于描述领域的某个方面而本身没有概念标识的对象称为 Value Object 。 Value Object 被实例化之后用来表示一些设计元素,我们只关心这些元素是什么,而不关心它们是谁。(没有个体特别的区别)
Value Object 可以是其他对象的集合。如窗户是由窗户样式长宽高等值对象组合成的复杂Value Object 。
Value Object 可以引用Entity。如一条泉州到厦门的地图导航路线,路线是Value Object ,而两个城市是Entity。
当我们只关心一个模型元素的属性时,应把它归类为Value Object 。我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。Value Object 应该是不可变的。不要为它分配任何标识,而且不要把它设计成像Entity那么复杂。
下图中的address对象就是个值对象:
值对象的共享:享元模式。 例子:房屋设计软件中的电源插座可以用享元模式来实现。
值对象的选择:是使用复制,还是使用共享。
无论是否共享Value Object ,在可能的情况下都要将它们设计为不可变的。
通过复制(冗余),可以将这种作为很多Entity属性的Value Object 存储在Entity所在的同一页上。这种存储相同数据的多个副本的技术称为非规范化。(denormalization) 当访问时间比存储空间或维护的简单性更重要时,使用这种技术。
模型中的关联越少越好,越简单越好。
模式:Service (服务)
Service 强调的是与其它对象的关系。
当领域中的某个重要的过程或装换操作不属于实体或值对象的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为Service 。定义接口时要使用模型语言,并确保操作名称是通用语言的术语。且Service 定义应该是无状态的。
领域对象与外部资源之间可以用外观模式把外部的Service 包装起来。
模式:Module(或Package)模块或包
Module提供两种观察模型的方式:
可以在Module中查看细节,而不会被整个模型淹没。
观察Module之间的关系,而不考虑其内部细节。
基于可以作为模块的概念组织的模块可以以一种有意义的方式将元素集中到一起,找到一种低耦合的概念组织方式,从而可以相互独立地理解和分析这些概念。
例子:Java中的包编码惯例
除非真正有必要将代码分布到不同的服务器上,否则就把实现单一概念对象的所有代码放在同一个模块中。
建模范式——主流的范式是面向对象设计
项目构建时应主动拥抱主流成熟的基础设施和工具支持,解决开发资源和避开技术风险。
P98使用不成熟范式带来的风险故事。
涉及大量数学问题的领域或者受全局逻辑推理控制的领域不适合使用面向对象范式。
其他范式:逻辑范式
当将非对象元素混合到以面向对象为主的系统中时,需要遵循的4条经验规则:
不要和实现范式对抗。
把通用语言作为依靠的基础。
不要一味依赖UML。
保持怀疑态度。
模式:Aggregate 集合
我们在程序中需要用到的对象集合体,如下的汽车对象(订单中心里的一个订单项,里面包含调度信息,业务订单信息,客户订单信息等)
固定规则是指在数据变化时必须保持不变的一致性规则。
模式:Factory 工厂
对象的创建本身可以是一个主要操作,但被创建的对象并不适合承担复杂的装配操作。将这些职责混合在一起可能导致出现难以理解的拙劣设计。让客户直接负责创建对象又会使客户的设计陷入混乱,并且破坏被装配对象或计划的封装,而导致客户与创建对象的实现之间产生过于紧密的耦合。因此,用工厂模式创建对象是极好的。
好的工厂的要求:
每个创建方法都是原子方法,而且满足被创建对象或集合的所有固定规则。如果无法正确创建,则应该抛出异常等,以确保不会返回错误的值。
工厂应该被抽象为所需的类型,而不是创建出具体的类。(伸缩性)
以下情况直接使用构造函数new创建即可:
类是一种类型。它不是任何相关层次结构的一部分,而且也没有通过接口实现多态性。(就是个类,如bean)
客户关心的是实现,可能是将其作为选择策略的一种方式
客户可以访问对象的所以属性,因此向客户公开的构造函数中没用嵌套的对象创建。
构造并不复杂
模式:Repository 存储
在所以持久对象中,有一小部分必须能够通过基于对象属性的搜索来全局访问(即如通过手机号码/身份证号码等特殊属性值区分来查询获取对象)。当不易通过遍历的方式来访问某些集合的根的时候,就需要使用这种访问方式(即不能通过如订单项来获取业务订单数据的时候,要通过上述的查询,比如通过客户订单号来获取业务订单数据)。它们通常是Entity,有时是具有复杂内部结构的Value Object,有时还可能是枚举Value。而其他对象则不宜使用数据库搜索访问获取对象数据的方式,因为这会混淆它们之间的重要区别。(即有些对象数据通过数据库来获取会破坏整个订单项的构成)。毫无约束的数据库查询可能会破坏领域对象的封装和集合。技术基础设施和数据库访问机制的暴露会增加客户的复制度,并妨碍模型驱动的设计。
只为那些确实需要直接访问的集合提供Repository。让客户始终聚焦于模型,而将所有对象存储和访问操作交给Repository来完成。
数据库查询时要注意检索到的数据对象不能无限大,可能会导致内存爆满溢出。
疑问
Entity 和 Value Object 的区别?怎么理解P85页上地址什么时候是实体,什么时候是值对象?
Service 定义应该是无状态的指的是不能有类成员变量?
WebSphere是什么?
7、使用语言:一个扩展的实例
P131实例,详细了解作者的针对一个案例的领域驱动设计思维全过程。跳过,待回头细读。
疑问
三、通过重构来加深理解
想成功开发出实用的模型的三点注意:
复杂巧妙的领域模型是可以实现的,也是值得我们去花费力气实现的。
这样的模型离开不断的重构是很难开发出来的,重构需要领域专家和热爱学习领域知识的开发人员密切参与进来。
要实现并有效地运用模型,需要警惕设计技巧。
重构定义:重构就是在不改变软件功能的前提下重新设计它。
设计模式重构
微重构
深层模型能够穿过领域表象,清楚地表达出领域专家们的主要关注点以及最相关的知识。
8、突破
重构前:
重构后:
疑问
什么叫微重构?
9、将隐式概念转变为显示概念(重要)
若开发人员识别出设计中隐含的某个概念或是在讨论中受到启发而发现一个概念时,就会对领域模型和相应的代码进行许多转换,在模型中键入一个或多个对象或关系,从而将次概念显式地表达出来。
当倾听领域专家的语言时,是否有一些术语能够简洁地表达出复杂的概念;有没有被他们提醒或纠正过描述用词?当你描述的特定短语时他们是否流露出迷惑的标签?如果有则暗示着某个概念也许可以改进模型。
P163 实例 待详细解读
不同的领域专家对同样的事情会有不同的看法,这取决于他们的经验和需求。
在寻找模型概念时,可以通过查阅书籍来解释和理解基本概念。
阅读书籍并不能提供现成的解决方案,但可以为他提供一些全新的实验起点,以及在这个领域中探索过的人总结出来的经验。这样可以避免开发人员重复设计已有的概念。
约束是模型概念中非常重要的类别。它们通常是隐式出现的,将它们显式地表现出来可以极大地提高设计质量。
例子如下:
显式地约束:
把约束条件提取成"有名有姓"的概念
需要修改约束设计的情况:
计算约束所需的数据从定义上看并不属于这个对象。
相关规则在多个对象中出现,造成了代码重复或导致不属于同一族的对象之间产生了继承关系。
很多设计和需求讨论是围绕这些约束进行的,而在代码实现中,它们却隐藏在过程代码中。
模式:Specification(规格)
为特殊目的创建谓词形式的显式的Value Object. Specification就是一个谓词,可用来确定对象是否满足某些标准。
疑问
单元测试?
10、柔性设计
模式:Intention-Revealing Interfaces(意图展示接口)
意图展示接口清楚地表明了用途。
思考:如果代码只是在执行规则后得到结果,而没有把规则显式地表达出来,那么我们就不得不一步一步的去思考软件的执行步骤。
对象的强大功能是它能够把所有这些细节封装起来,这样客户代码就能够很简单,而且可以用高层概念来解释。
如果开发人员为了使用一个组件而必须要去研究它的实现,那么就失去了封装的价值。(所有要展示组件的意图)
类和方法的名称为开发人员之间的沟通创造了很好的机会,也能改善系统的抽象。
意图命名原则:
命名类和操作时要描述它们的效果和目的,而不用表露它们是通过何种方式达到目的地。这样可以使客户开发人员不必去理解内部的细节。在创建一个行为之前先为它编写一个测试,这样可以促使你站在客户开发人员的角度上来思考它。
在领域的公共接口中,可以把关系和规则表述出来,但不要说明规则是如何实施的;可以把事件和动作描述出来,但不要描述它们是如何执行的;可以给出方程式,但不要给出解方程式的数学方法。可以提出问题,但不要给出获取答案的方法。
命名的案例:
模式:side-effect-free function(无副作用的函数 )
副作用指的是意外的结果。
如果没有了可以安全预见到结果的抽象,开发人员就必须限制“组合爆炸”,这样会限制系统行为的丰富性。
返回结果而不产生副作用的操作称为函数,让我们能够更准确地预测结果。
尽可能把程序的逻辑放在函数中,因为函数是只返回结果而不产生明显副作用的操作。严格地吧命令(引起明显的状态改变的方法)隔离到不返回领域信息的、非常简单的操作中。当发现了一个非常适合承担复杂逻辑职责的概念时,就可以把这个复杂逻辑移到Value Object(利用其不变性)中,这样可以进一步控制副作用。
模式:Assertion(断言)
使用Entity时有些有副作用的命令,使用人必需了解使用这些命令的后果。(如Entity里某个值不能为NULL)
这种情况下,使用断言可以把副作用明确地表示出来,使他们更易于处理。
断言是前置条件(契约式设计 design by contract)。前置条件就像是合同条款,即为了满足后置条件而必须要满足的前置条件。类的固定规则规定了在操作结束时对象的状态。
模式:Conceptual contour (概念上的轮廓)一般发生中后期在重构上
把设计元素(操作、接口、类和集合)分解为内聚的单元,在这个过程中,你对领域中一切重要划分的直观认识也要考虑在内。在连续的重构过程中观察发生变化和保证稳定的规律性,并寻找能够解释这些变化模式的底层概念轮廓。使模型与领域中那些一致的方面相匹配。
模式:Standalone Class (孤立的类)
Module 和 Aggregate 的目的都是为了限制互相依赖的关系网。但即使在Module内部,设计也会随着依赖关系的增加而变得越来越难以理解。这加重了我们的思考负担,从而限制了开发人员能处理的设计复杂度。隐式概念比显示的引用增加的负担更大。
低耦合是对象设计的一个基本要素。尽一切可能保持低耦合。把其他所有无关概念提取到对象之外。这样类就变得完全孤立了,这使得我们可以单独地研究和理解它。每个这样孤立的类都极大地减轻了因理解Module 而带来的负担。(单一职责)
尽力吧最复杂的计算提取到Standalone Class 中,可能实现此目的的一种方法是把具有紧密联系的类中所含有的Value Object 建模出来。
模式:Closure of Operation(闭合操作)
当我们对集合中的任意两个元素组合时,结果仍然在这个集合中,这就叫闭合操作。
大部分引起我们兴趣的对象所产生的行为仅用基本类型是无法描述的。
在适当的情况下,在定义操作时让它的返回类型与其参数的类型相同。(我们一般查询页面的出入参相同)如果实现者的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数和返回值应该与实现者有相同的类型。这样的操作就是在该类型的实例集合中的闭合操作。闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖性。
Model-Driven Design 的作用受细节设计的质量和实现决策的质量影响很大,而且只要有少数几个开发人员没有弄清楚它们,整个项目就会偏离目标。
specification 实现
原则:尽可能利用已有的形式。如会计学定义了一组成熟的Entity和规则,我们很容易对这些Entity和规则进行调整,得到一个深层的模型和柔性设计。(这也就是乔布斯说的好的艺术家抄袭,伟大的艺术家剽窃 。或者其实就是牛顿说的站在巨人的肩膀上。 )作者最喜欢的概念框架是数学——它能用基本数学概念把一些复杂的问题提取出来。
疑问
在领域的公共接口中,可以把关系和规则表述出来,但不要说明规则是如何实施的;可以把事件和动作描述出来,但不要描述它们是如何执行的;可以给出方程式,但不要给出解方程式的数学方法。可以提出问题,但不要给出获取答案的方法。这一段话如何理解?
11、分析模式的应用
要点:如何将分析模式集成到领域驱动设计过程中。
分析模式是一种概念集合,用来表示业务建模中的常见构造。它可能只与一个领域有关,也可能跨越多个领域。
分析模式并不是技术解决方案,而只是用来指导人们设计特定领域中的模型。分析模式的最大作用是借鉴其他项目的经验,把那些项目中所做的广泛的设计方向讨论和实现结果的经验与当前的模型结合起来。
依赖性设计中的三种处理方法(业务的后续处理):
主动触发。(如调用接口)
基于主体(Account的触发)允许推迟处理。(如通过发送消息)
有外部代理(调度任务)来启动触发。一个定时触发任务(基于过账规则的触发,如每天凌晨的对账调度任务)
分析模式为我们提前预测了一些后期结果,避免我们如果自己去实践可能付出的高昂的代价。
疑问
12、将设计模式应用于模型
设计模式从代码的角度来看它们是技术设计模式,从模型的角度来看它们就是概念模式。
策略模式
领域模型包含一些并非用于解决技术问题的过程,将它们包含进来是因为他们对过程问题领域具有实际的价值。当必须从多个过程中进行选择时,选择的复杂性再加上多个过程本身的复杂性会使局面失去控制。(开发和维护人员很难理解一堆的if、else及庞大的代码块)
我们需要把过程中的易变部分提取到模型的一个单独的策略对象中。将规则与它所控制的行为区分开。按照策略设计模式来实现规则或可替换的过程。策略对象的多个版本表示了完成过程的不同方式。
组合模式
使用的前提:当嵌套容器的关联性没有在模型中反映出来时,公共行为必然会在层次结构的每一层重复出现,而且嵌套也变得僵化。客户必须通过不同的接口来处理层次结构中的不同层,尽管这些层可能在概念上没有区别。通过层次结构来递归地收集信息也变得非常复杂。
定义一个把组合的所有成员都包含在内的抽象类型(父类,子类有叶节点和容器节点),在容器上实现一些用来查询信息的方法,这些方法可用来收集与容器内容有关的信息。叶节点基于它们自己的值来实现这些方法(有些叶节点不具备的方法也要实现,不过要抛出异常或者返回空)。这样客户就只需使用抽象类型,而无需区分叶节点和容器。
设计模式运用于领域模式的基本特征是其既适用于模型,也适用于实现。
把设计模式用作领域模式的唯一要求是这些模式能够描述关于概念领域的一些事情,而不仅仅是作为解决技术问题的技术解决方案。
疑问
13、通过重构得到更深层的理解
重构的核心:
以领域为本。
用一种不同的方式来看待事物。
始终坚持与领域专家对话。
(领域模型驱动)重构的原因:
模型的语言没有与领域专家保持一致,或者新的需求不能被自然地添加到模型中。
开发人员通过学习获得了更深刻的理解,从而发现了一个得到更清晰或更有用的模型的机会。
团队:
包含领域专家的4-5个人的小组,其中开发人员应该擅长思考该类型问题,了解领域,或者掌握深厚的建模技巧。
保证效率:
自主决定。一个临时的头脑风暴团队,只工作几天。
注意范围和休息。如果讨论时间和范围都太长导致毫无进展,那么可以选择一个较小的设计方面,集中讨论它。
练习使用通用语言来讨论。
在模型设计中,经常需要修改的地方要能够保持很高的灵活性,而其他地方则相对比较简单。
重构的时机:
设计没有表达出团队对领域的最新理解。
一些重要的概念被隐藏在设计中了(开发人员能这些概念呈现出来)。
发现了一个能令某个重要的设计部分变得更灵活的机会。
注意:项目发布前不要重构;不要引入一些只顾炫耀技术能力而没有解决领域核心问题的柔性设计。
疑问
一个系统过于庞大的话那么还有必要重构么?
四、战略设计
战略设计用于大而复杂的系统领域设计,通过团队乃至多个团队进行决策指定。
首先便是分割模块化设计业务模型,在不损害集成利益的前提下。
进行模块化领域设计是因为如果设计一个把所有概念都涵盖进来的单一领域模型,它将会非常笨拙且会出现大量难以察觉的重复和矛盾。会么?
三大主题:上下文、精炼、大比例结构。
结构和精炼能够帮助我们理解各个部分之间的复制关系,同时保持整体视图的清晰。Bounded Context 使我们能够在不同的部分中进行工作,而不会破坏模型或是无意间导致模型的分裂。
14、保持模型的完整性
大型系统领域模型的完全统一是不可行的,也不是一种经济有效的做法。
除技术上的因素外,权力上的划分和管理级别的不同也要求把模型分开。
因为有以下风险:
一次尝试对遗留系统做过多的替换。
大项目可能会陷于困境,因为协调的开销太大,超出了这些项目的能力范围。
具有一些特殊需求的应用程序可能不得不使用无法重复满足需求的模型,而只能将这些无法满足的行为放到其他地方。
试图用一个模型来满足所有人的需求可能会导致模型中包含过于复杂的选择,因而很难使用。
模式:Bounded Context(有界的上下文)
明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
要想避免共享模型产生的问题(含义和代表的意思是否不同等),每个人都必须理解模型上下文的边界在哪里。
模式:Continuous Integration(连续集成)
Continuous Integration是指把一个上下文中的所有工作足够频繁地合并到一起,并使它们经常保持一致,以便当模型发生分裂时,可以迅速发现并纠正问题。
连续集成的操作:
模型概念的集成。
实现的集成。
建立一个经常把所有代码和其他实现工件合并到一起的过程,并通过自动测试来快速查明模型的分裂问题。严格坚持使用通用语言,以便在不同人的头脑中演变出不同的概念时,使所有人对模型都能达成一个共识。
模式:Context Map()
识别每个模型在项目中的作用,并定义其Bounded Context。这包括非面向对象子系统的隐含模型。为每个Bounded Context命名,并把名称添加到通用语言中。描述模型之间的接触点,明确每次交流所需的转换,并突出任何共享的内容。
画出现有的范围。为稍后的转换做好准备。
两个重点:
Bounded Context 应该有名称,以便可以讨论它们。
每个人都应该知道边界在哪里,而且能够分辨出任何代码段的Context,或任何情况的Context。
建议:
开发一个紧密集成产品的优秀团队可以部署一个大的、统一的模型、如果团队需要为不同的用户群提供服务,或者团队的协调能力有限,可能就需要采用Shared Kernel (共享内核)或Customer/Supplier(客户/供应商)关系。如果集成并不重要,系统最好采用Separate Way(独立自主)模式。大多数项目都需要与遗留系统或外部系统进行一定程度的集成,这就需要使用Open Host Service (开发主机服务)或Anticorruption Layer(防护层)。
模式:Shared Kernel (共享内核)
从领域模型中选出两个团队都同意共享的一个子集,包括代码子集或数据库设计的子集。这部分明确共享的内容在一个团队没有与另一个团队商量的情况下不应擅自更改它。在进行集成时,两个团队都要运行测试。(例子:carbase库依赖,DTO的共享等)
模式:Customer/Supplier Development Team(客户/供应商开发团队)关系
使用场景:一个子系统的主要任务是服务于另一个子系统。
在两个团队之间建立一种明确的客户/供应商关系。下游团队相当于上游团队的客户。根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道双方的约定和进度。
两个团队一起开发自动验收测试,用来验证预期的接口。在迭代期间,下游团队成员应该像传统的客户一样随时回答上游团队的提问,并帮助解决问题。
模式:Conformist(跟随者)
应用场景:两个上下游关系的团队无法配合工作时,上游团队处于利他主义的考虑,做出他们可能不会履行的承诺,下游出于良好的意愿相信这些承诺,从而根据一些永远不会实现的特性来制定计划时。
当接口很大而且集成更加重要时,跟随是有意义的。
在这种情况下,有3种结局途径。
完全放弃对上游的利用。(Separate Way(独立自主))
根据上游设计的质量和风格,如果上游的设计很难使用,那么下游必须咖啡其资金的模型,开发一个转换层。Anticorruption Layer(防护层)。
根据上游设计的质量和风格,如果上游的设计质量不是很差,风格可以兼容,那么可以使用Conformist(跟随者)模式。
模式:Anticorruption Layer(防护层)
与遗留系统的交互。
创建一个隔离的层,以便根据客户自己的领域模型来为客户提供相关的功能。这个层通过其现有接口与另一个系统进行对话,而只需对那个系统做出很少的修改,甚至无需修改。在内部,这个层在两个模式之间进行必要的双向转换。
Anticorruption Layer 设计进行组织的一种方法是把它实现为外观、适配器这两种模式和转换器组合。
模式:Separate Way(独立自主)
我们必须严格划定需求的范围。如果两组功能之间的关系并非必不可少,那么两者完全可以彼此独立。
集成代价总是高昂,而有时获益却很小。所以独立开发时,声明一个与其他上下文毫无关联的Bonded Context,使开发人员能够在这个小范围内找到简单、专用的解决方案。
模式:Open Host Service (开发主机服务)
定义一个协议,把你的子系统作为一组Service供其他系统访问。开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。满足这种特殊需求的方法是使用一次性的转换器来扩充协议,以便使共享协议简单且内聚。
模式: Published Language(公开发布的语言)
如XML,JSON等用于两个系统或者模型之间的交互响应。
与现有领域模型之间进行直接的转换可能不是一种好的解决方案。这些模型可能过于复杂或者设计得较差。如果把其中一个模型作为数据交换的语言(如Facade模式提供接口交互),那么他实质上就被固定住了,而无法满足新的开发需求。
把一个良好文档化的、能够表达出所需领域信息的共享语言作为公共的通信媒介,必要时在其他信息与该语言之间进行转换。
“大象”的统一(从盲人摸象看模型集成统一)
这一节很重要。
虽然盲人共享更多的大象信息可以得到更大的价值,但没有人愿意放弃自己的模型而采用别人的模型。
第一等到盲人们认识到这是在对一个更大整体的不同部分进行描述和建模时:下面这时候得到的模型还是简陋的,缺乏内聚性,也没有形成一个底层的领域模型。如大象是可以移动的,移动起来树理论上就不成立了,而应该是腿的概念。
第二是去掉各个模型中那些偶然或不正确的方面,并创建新的概念。成功的模型应该尽可能做到精简,宁“少”勿多。如大象宁可缺少喷水功能,也不用包含不正确的蛇的毒牙特性。
承认多个互相冲突的领域模型实际上正是面对现实的做法。通过定义每个模型都适用的上下文,可以维护每个模型的完整性,并清除地看到要在两个模型创建的任何特殊接口的含义。
最重要的是,每个人都要承认每个模型各自的理解是不完整的。作为出发点去改善模型。
由于很多时候我们不是从头开发一个项目,而是改进一个正在开发的项目。这时候第一步是根据当前的状况来定义Bounded Context.Context Map必须反映出团队的实际工作,而不是反映那个通过遵守以上描述的指导原则而得出的理想组织。
遗留系统设计得越好,它就越容易被淘汰。(就像离乱麻)
疑问
什么是集成利益?
大型系统领域模型的完全统一是怎么个统一法?其实在分模块上的模型在总的来说不也是统一的么?大家都从这个模块上获取数据计算、调用统一接口什么的,比如电商中的客户管理系统?
怎么理解连续集成?
上游团队与下游团队是怎么个定义法?什么叫上下游?
A:上游:上游一般指本企业的材料,产品供应商 ;下游:一般就是企业生产品的经销、分包单位 .
以Service层为例:他的上游就是Dao;下游就是Controller。下游团队的代表类似于用户代表,发起请求的客户端。下游团队依赖于上游团队。
15、精炼
模型就是知识的精炼。
像化学蒸馏一样,精炼的主要动机就是把最有价值的部分提取出来,正是这部分使我们的软件区别于别的软件,这部分叫做Core Domain.
领域模型的战略精炼包括以下部分:
帮助所有团队成员掌握系统的总体设计及协调。
找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通。
指导重构。
专注于模型中最有价值的那部分。
指导外包、现有组件的使用以及任务委派。
模式:Core Domain(系统中最有价值的部分)
注意,在设计大型系统时,有很多有用的组件,他们很复杂且绝对有必要把它们做好,这导致真正的业务资产——领域模型被掩盖和忽略了。
在制定项目规划时,必须把资源分配给模型和设计中最关键的部分。而不能只是让技术能力最强的开发人员只是去捣鼓技术基础设施,反而把业务模型设计交给不熟悉对象技术的新人手中。
对模型进行提炼。最有价值和最专业的概念要轮廓分明。尽量压缩Core Domain.
让最有才能的人来开发Core Domain。如果某个设计部分需要保密以便保持竞争优势,那么它就是Core Domain。
项目团队中,技术能力 最强的人员往往缺乏丰富的领域知识。
模式:Generic Subdomain(通用子领域)
提取通用子领域,在开发过程中,通用子领域的优先级应低于核心领域的优先级,且不要分派核心开发
对于通用子领域,应该能把它们识别出来。在开发过程中,其优先级应低于核心领域的优先级,不派发给核心开发人员来完成,可以考虑使用现成的解决方案或公开发布的模型。
1、现成的解决方案。购买解决方案或使用开源代码。
优点:
减少代码开发。
维护负担转移到了外部。
代码在其他公司等地方使用过,可能较为成熟,因此比自己开发的代码更可靠和完备。
缺点:
使用前仍然需要花时间评估和理解它。
就业内目前的质量控制水平而言,无法保证它的正确性和稳定性。(如开源的常常有些坑)
它可能设计得过于细致了(远超自己想要的目的),集成的工作量可能比开发一个最小化的内部实现更大。
外部元素的集成常常不顺利。可能上下文完全不同,或者难以引用其他软件包中的Entity.
它可能引入对平台、编译器版本的依赖等。
通用子领域最好是被打包成框架的形式,实现了非常抽象的模型,从而可以与我们的应用程序集成来满足特殊需求。
子组件越通用,其模型的精炼程度越高,它的用处可能就越大。
2、公开发布的设计或模型
优点:
比自己开发的模型更为成熟,并且反映了很多人的深层知识。
提供了随时可用的高质量文档。
缺点:
- 可能不是很符合你的需要,或者设计得过于细致。
在领域建模中,特别是在攻克通用子领域时,通过抄袭留意别人的成果和工作来复制其设计和模型,并把这叫做研究。因为当有一个广泛使用的模型时,这些模型不仅健壮和流畅,且被人们广泛理解,减轻了培训负担。意思就是如果没有必要,不要去重复造轮子了。
3、把实现外包出去
优点:
使核心团队可以脱身去处理core domain,这是最需要知识和经验积累的部分。
开发工作的增加不会使团队规模无限扩大下去,同时又不会导致core domain知识的分散。
强制团队采用面向接口的设计,有助于保持子领域的通用性。
缺点:
需要核心团队花费一些时间与外包人员商量接口、编码标准和其他重要方面。(联调沟通实在效率太低了)
需要消耗大量精力来理解这些代码。
代码质量不一,取决于两个团队的能力的高低。
这里自动测试在外包中可能起到重要作用,要求交付的代码附有单元测试。最有效的是为这些组件指定甚至编写自动验收测试。第二点和第三点可以结合在一起。
4、内部实现
优点:
易于集成。
只开放自己需要的,不做多余的工作。
可以临时把工作分包出去。
缺点:
需要承受后续的维护和培训负担。
很容易低估开放这些软件包所需的时间和成本。
案例:项目时区问题的处理方式(p307)
通用不等于重用,不需要特别关注子领域代码的可重用性,因为这会违反精炼的基本动机——尽可能把大部分精力投入到core domain工作中。
注意,如果是通用子领域,那么其设计则必须严格地限定在通用概念的范围之内。
项目风险管理
敏捷过程通常要求通过尽早解决最具风险的任务来管理风险。
项目一般面临两方面的风险,有些项目的技术风险更大,有些项目则是领域建模的风险更大一些。
除非团队技术精湛且对领域非常熟悉,否则第一个雏形系统应该以CORE DOMAIN的某个部分作为基础。
模式: Domain Vision Statement(领域前景说明)
做法:写一份Core Domain 的简短描述以及它将会创造的价值,那些不能将领域模型与其他领域模型区分开的方面就不要写了。展示出领域模型是如何实现和均衡各方面利益的。尽量精简,尽早写出来,等到获得新的理解后再修改它。
对比:
标明Core
对于拥有详细“领域模型”的文档,我们应该从中大幅删减,找到一个小的core domain 并重构它,再逐步添加其他细节。标识出core domain ,弄出个模型的导航图。
把模型的主要存储库中的core domain 标记出来,而不要特意去阐明其角色。使开发人员很容易就知道什么在核心内,什么在核心外。
模式:Cohesive Mechanism(内聚)
对比于策略模式
Cohesive Mechanism用来满足规则或者用来完成模型指定的计算。
注意:算法与领域模型应该分离。
模式:Segregated Core(隔离核心)
p319示例
模式:Abstract Core(抽象核心)
深层模型精炼
尽管任何一次突破都会得到一个有价值的深层模型,但只有Core Domain 中的突破才能改变整个项目的轨道。
选择重构目标:
如果采用“那儿痛治哪”这种重构策略,要观察一下根源问题是否涉及core domain 或core与支持元素的关系。如果确实涉及,那么就要接受挑战,首先修复核心。
当可以自由选择重构的部分时,应首先集中精力把core domain 更好地提取出来,完善对core的隔离,并且把支持性的子领域提炼成通用子领域。
疑问
16、大比例结构
在一个大的系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式中的角色来解释这些元素,那么开发人员就会陷入“只见树木,不见森林”的境地。
“大比例结构”是一种语言,人们可以用它来从大局上讨论和理解系统。
模式:evolving order
使用场景:
一个没有任何规则的随意设计会产生一些无法理解整体含义且很难维护的系统。但架构中预先规定的假设又会使项目变得束手束脚,而且会极大地限制应用程序中某些特定部分的开发人员、设计人员的能力。很快,开发人员就会为适应结构而不得不在应用程序的开发上委曲求全,要么就是完全推翻架构而又回到不协调的开发的老路上来。
使用:
应该允许这种概念上的大比例结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。有些设计决策和模型决策必须在掌握了详细知识之后才能确定,这样的决策不必过早的指定。
宁缺毋滥原则。
模式:System Metaphor(系统隐喻)
模式:Responsibility Layer(职责分层)
待阅读。
疑问
17、领域驱动设计的综合应用
待阅读。
结束语
检验软件成功与否的最有效的方法是让它运行一段时间。
虽然开发最前沿的项目并体验有趣的思想和工具会带来巨大的成就感,但如果软件得不到有效的应用,那么一切都将成为空谈。
对自己的设计不要持有守旧情结,随着大量的深层领域知识和经验的积累,便需要根据这些理解对模型和设计进行转换,一个成功的设计并不一定要永远保持不变。变更是软件的固有性质。
开发小的,确保能够交付的应用程序,并坚持用最简单的设计来实现简单的功能。这种保守的方法有它自己的用武之地,可以使项目范围保持精简,并且使项目具有快速响应的能力。但集成的、模型驱动的系统所提供的价值是那些拼凑起来的系统无法提供的。我们应该采用的做法是使用领域驱动的设计构建一个深层的模型和柔性设计,这样,具有丰富功能的大型系统就能逐步增长。
是否采用了领域驱动设计的标志性的特征是“把理解目标领域并把学到的知识融合到软件中当做首要任务。团队成员在项目中有意识地使用通用语言,并且不断对语言进行精化。
虽然掌握了一些初级技术的众多编程人员可以开发出特定种类的软件,但他们绝对无法开发出能在危急关头拯救公司的软件。
工具构建人员必须确保他们开发出的工具能够提高那些优秀软件开发人员的能力和工作效率。
领域驱动设计提供了一种标准化,让我们能更加通用得使用、维护和理解使用了领域驱动设计的软件,就像丰田、本田车的标准化生产和维修一样,而不像标致一样,不通用也别人不同。
领域开发缺乏标准的设计元素,因此每个领域模型和对应的实现都很奇怪且难以理解。
以标准的模式元素为基础,可以避免把精力浪费在那些已经存在了解决方案的问题上,从而集中精力关注我们的特殊问题。根据传统的模式来建立自己的设计可以避免产生一个过于特殊化的、难交流的设计。
模式名称应该作为团队语言中的术语来使用。
P375 术语表
疑问:
什么样的公司文化才有利于迭代开发?