Shellbye.github.io icon indicating copy to clipboard operation
Shellbye.github.io copied to clipboard

《领域驱动设计——软件核心复杂性应对之道》读书笔记

Open Shellbye opened this issue 5 years ago • 0 comments

第一部分 运用领域模型
    模型在领域驱动设计中的作用
        1.模型和设计的核心互相影响
        2.模型是团队所有成员使用的通用语言的中枢
        3.模型是浓缩的知识
    软件的核心
        软件的核心是其为用户解决领域相关的问题的能力

第1章 消化知识
    1.1 有效建模的要素
        1.模型和现实的绑定
        2.建立了一种基于模型的语言
        3.开发一个蕴含丰富知识的模型
        4.提炼模型
        5.头脑风暴和实验
    1.2 知识消化
    1.3 持续学习
    1.4 知识丰富的设计
    1.5 深层模型

第2章 交流与语言的使用
    2.1 模式: Ubiquitous(普及) language
    2.2 “大声地” 建模
    2.3 一个团队,一种语言
    2.4 文档和图
            设计的重要细节应该在代码中体现出来
            模型不是图
        2.4.1 书面设计文档
            文档应作为代码和口头交流的补充
                1.极限编程主张完全不使用(多余的)设计文档,而让代码解释自己
                2.将代码作为设计文档也有局限性。它可能会把读代码的人淹没在细节中。
                  尽管代码的行为是非常明确的,但这并不意味着其行为是显而易见的。而且行为背后的意义可能难以表达。
                3.开发人员并不是唯一需要理解模型的人
            文档不应再重复表示代码已经明确表达出的内容
            文档应当鲜活并保持最新
        2.4.2 完全依赖可执行代码的情况
    2.5 解释性模型

第3章 绑定模型和实现
    3.1 模式: Model-driven design
        如果整个程序设计或者其核心部分没有与领域模型相对应,那么这个模型就是没有价值的,软件的正确性也值得怀疑。
        同时,模型和设计功能之间过于复杂的对应关系也是难于理解的,在实际项目中,当设计改变时也无法维护这种关系。
        若分析与和设计之间产生严重分歧,那么在分析和设计活动中所获得的知识就无法彼此共享。
    3.2 建模范式和工具支持
    3.3 揭示主旨: 为什么模型对用户至关重要
    3.4 模式: Hands-on modeler (亲身实践的建模者)
        如果编写代码的人员认为自己没必要对模型负责,或者不知道如何让模型为应用程序服务, 那么这个模型就和程序没有任何关联。
        如果开发人员没有意识到改变代码就意味着改变模型,那么他们对程序的重构不但不会增强模型的作用,反而还会削弱它的效果。
        同样,如果建模人员不参与到程序实现的过程中,那么对程序实现的约束就没有切身的感受,即使有,也会很快忘记。 
        MODEL-DRIVEN DESIGN的两个基本要素(即模型要支持有效的实现并抽象出关键的领域知识)已经失去了一个,最终模型将变得不再实用。
        最后一点,如果分工阻断了设计人员与开发人员之间的协作,使他们无法转达实现MODEL-DRIVEN DESIGN的种种细节,那么经验丰富的设计人员则不能将自己的知识和技术传递给开发人员。

        任何参与建模的技术人员,不管在项目中的主要职责是什么,都必须花时间了解代码。
        任何负责修改代码的人员则必须学会用代码来表达模型。每一个开发人员都必须不同程度地参与模型讨论并且与领域专家保持联系。
        参与不同工作的人都必须有意识地通过UBIQUITOUS LANGUAGE 与接触代码的人及时交换关于模型的想法。

第二部分 模型驱动设计的构造块
第4章 分离领域
    4.1 模式: Layered architecture
            在面向对象的程序中,常常会在业务对象中直接写入用户界面、数据库访问等支持代码。
            而一些业务逻辑则会被嵌入到用户界面组件和数据库脚本中。这么做是为了以最简单的方式在短期内完成开发工作。
            如果与领域有关的代码分散在大量的其他代码之中,那么查看和分析领域代码就会变得异常困难。
            对用户界面的简单修改实际上很可能会改变业务逻辑,而要想调整业务规则也很可能需要对用户界面代码、数据库操作代码或者其他的程序元素进行仔细的筛查。
            这样就不太可能实现一致的、模型驱动的对象了,同时也会给自动化测试带来困难。
            考虑到程序中各个活动所涉及的大量逻辑和技术,程序本身必须简单明了,否则就会让人无法理解。

            ------------------|--------------------------------------------------------------------------------------
            用户界面层(或表示层)|  负责向用户显示信息和解释用户指令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人
            ------------------|--------------------------------------------------------------------------------------
            应用层             |  定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,
                              |  也是与其他系统的应用层进行交互的必要渠道
                              |  应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作。
                              |  它没有反映业务情况的状态,但是却可以具有另外一种状态,为用户或程序显示某个任务的进度
            ------------------|--------------------------------------------------------------------------------------
            领域层(或模型层)   |  负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,
                              |  但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心
            ------------------|--------------------------------------------------------------------------------------
            基础设施层          |  为上面各层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件,等等。
                              |  基础设施层还能够通过架构框架来支持4个层次间的交互模式
            ------------------|--------------------------------------------------------------------------------------

            给复杂的应用程序划分层次。在每一层内分别进行设计,使其具有内聚性并且只依赖于它的下层。
            采用标准的架构模式,只与上层进行松散的耦合。将所有与领域模型相关的代码放在一个层中,并把它与用户界面层、应用层以及基础设施层的代码分开。
            领域对象应该将重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题,也无需管理应用任务等内容。
            这使得模型的含义足够丰富,结构足够清晰,可以捕捉到基本的业务知识,并有效地使用这些知识。
        4.1.1 将各层关联起来
        4.1.2 架构框架
    4.2 领域层是模型的精髓
    4.3 模式: The smart UI “反模式”
    4.4 其他分离方式
第5章 软件中所表示的模型
    5.1 关联
        模型中每个可遍历的关联,软件中都要有同样属性的机制
    5.2 模式: Entity (又称为 Reference Object)
            主要由标识定义的对象被称作ENTITY
        5.2.1 Entity 建模
        5.2.2 设计标识操作
    5.3 模式: Value object 
            当我们只关心一个模型元素的属性时,应把它归类为VALUE OBJECT。
            我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。
            VALUE OBJECT应该是不可变的。不要为它分配任何标识,而且不要把它设计成像ENTITY那么复杂。
        5.3.1 设计 Value Object 
            以下几种情况最好使用共享,这样可以发挥共享的最大价值并最大限度地减少麻烦:
                1. 节省数据库空间或减少对象数量是一个关键要求时
                2. 通信开销很低时(如在中央服务器中)
                3. 共享的对象被严格限定为不可变时
        5.3.2 设计包含 Value Object 的关联
    5.4 模式: Service 
            一些领域概念不适合被建模为对象。如果勉强把这些重要的领域功能归为ENTITY或VALUE OBJECT的职责,
            那么不是歪曲了基于模型的对象的定义,就是人为地增加了一些无意义的对象。

            好的 Service 有以下3个特征
                1.与领域概念相关的操作不是 entity 或 value object 的一个自然组成部分
                2.接口是根据领域模型的其他元素定义的
                3.操作是无状态的
            
            当领域中的某个重要的过程或转换操作不是ENTITY或VALUE OBJECT的自然职责时,
            应该在模型中添加一个作为独立接口的操作,并将其声明为SERVICE。定义接口时要使用模型语言, 
            并确保操作名称是UBIQUITOUS LANGUAGE中的术语。此外,应该使SERVICE成为无状态的。
        5.4.1 Service 与孤立的领域层
        5.4.2 粒度
        5.4.3 对 Service 的访问
    5.5 模式: Module (也称为 Package )
            MODULE并不仅仅是代码的划分,而且也是概念的划分

            选择能够描述系统的MODULE,并使之包含一个内聚的概念集合。这通常会实现MODULE之间的低耦合,但如果效果不理想,
            则应寻找一种更改模型的方式来消除概念之间的耦合,或者找到一个可作为MODULE基础的概念(这个概念先前可能被忽视了),
            基于这个概念组织的MODULE 可以以一种有意义的方式将元素集中到一起。找到一种低耦合的概念组织方式,从而可以相互独立地理解和分析这些概念。
            对模型进行精化,直到可以根据高层领域概念对模型进行划分,同时相应的代码也不会产生耦合。
            MODULE的名称应该是UBIQUITOUS LANGUAGE中的术语。MODULE及其名称应反映出领域的深层知识。
        5.5.1 敏捷的 Module 
        5.5.2 通过基础设施打包时存在的隐患
    5.6 建模范式
        5.6.1 对象范式流行的原因
        5.6.2 对象世界中的非对象
        5.6.3 在混合范式中坚持使用 model-driven design 
第6章 领域对象的生命周期
    6.1 模式: aggregate
            在具有复杂关联的模型中,要想保证对象更改的一致性是很困难的。
            不仅互不关联的对象需要遵守一些固定规则,而且紧密关联的各组对象也要遵守一些固定规则。
            然而,过于谨慎的锁定机制又会导致多个用户之间毫无意义地互相干扰,从而使系统不可用
        为了实现这个概念上的AGGREGATE,需要对所有事务应用一组规则
            1. 根ENTITY具有全局标识,它最终负责检查固定规则
            2. 根ENTITY具有全局标识。边界内的ENTITY具有本地标识,这些标识只在AGGREGATE内部才是唯一的
            3. AGGREGATE外部的对象不能引用除根ENTITY之外的任何内部对象。根ENTITY可以把对内部ENTITY的引用传递给它们,
               但这些对象只能临时使用这些引用,而不能保持引用。
               根可以把一个VALUE OBJECT的副本传递给另一个对象,而不必关心它发生什么变化,
               因为它只是一个VALUE,不再与AGGREGATE有任何关联
            4. 作为上一条规则的推论,只有AGGREGATE的根才能直接通过数据库查询获取。所有其他对象必须通过遍历关联来发现
            5. AGGREGATE内部的对象可以保持对其他AGGREGATE根的引用
            6. 删除操作必须一次删除AGGREGATE边界之内的所有对象。(利用垃圾收集机制,这很容易做到。由于除根以外的其他对象都没有外部引用,因此删除了根以后,其他对象均会被回收。)
            7. 当提交对AGGREGATE边界内部的任何对象的修改时,整个AGGREGATE的所有固定规则都必须被满足
    6.2 模式: Factory
            一个对象在它的生命周期中要承 担大量职责。如果再让复杂对象负责自身的创建,那么职责过载将会导致问题。
            
            任何好的工厂都需满足以下两个基本需求:
                1. 每个创建方法都是原子的,而且要保证被创建对象或AGGREGATE的所有固定规则。 FACTORY生成的对象要处于一致的状态。
                2. FACTORY应该被抽象为所需的类型,而不是所要创建的具体类
        6.2.1 选择 Factory 及其应用位置
        6.2.2 有些情况下只需使用构造函数
            1. 类是一种类型。它不是任何相关层次结构的一部分,而且也没有通过接口实现多态性
            2. 客户关心的是实现,可能是将其作为选择 strategy 的一种方式
            3. 客户可以访问对象的所有属性,因此向客户公开的构造函数中没有嵌套的对象创建
            4. 构造并不复杂
            5. 公共构造函数必须遵守与 Factory 相同的规则: 它必须是原子操作,而且要满足被创建对象的所有固定规则
        6.2.3 接口的设计
        6.2.4 固定规则的相关逻辑应放置在哪里
        6.2.6 重建已存储的对象
            重建与创建主要有以下两点不同
                1. 用于重建对象的 Entity Factory 不分配新的跟踪 ID
                2. 档固定规则未被满足时,重建对象的 Factory 采用不同的方式进行处理
    6.3 模式: Repository
        优点
            1. 它们为客户提供了一个简单的模型,可用来获取持久化对象并管理它们的生命周期
            2. 它们使应用程序和领域设计与持久化技术(多种数据库策略甚至是多个数据源)解耦
            3. 它们体现了有关对象访问的设计决策
            4. 可以很容易将它们替换为“哑实现”(dummy implementation),以便在测试中使用(通常使用内存中的集合)
        6.3.1 Repository 的查询
        6.3.2 客户代码可以忽略 Repository 的实现,但开发人员不能
        6.3.3 Repository 的实现 
        6.3.4 在框架内工作
        6.3.5 Repository 与 Factory 的关系
    6.4 未关系数据库设计对象
第7章 使用语言: 一个扩展的示例
    7.1 货物运输系统简介
    7.2 隔离领域: 引入应用层
    7.3 将 Entity 和 value object 区别开
    7.4 设计运输领域中的关联
    7.5 aggregate 边界
    7.6 选择 Repository
    7.7 场景走查
        7.7.1 应用程序特性举例: 更改 Cargo 的目的地
        7.7.2 应用程序特性举例: 重复业务
    7.8 对象的创建
        7.8.1 Cargo 的 Factory 和构造函数
        7.8.2 添加 Handling Event
    7.9 停一下,重构: Cargo aggregate 的另一种设计
    7.10 运输模型中的 Module
    7.11 引入新特性: 配额检查
        7.11.1 连接两个系统
            ANTICORRUPTION LAYER
        7.11.2 进一步完善模型: 划分业务
            Enterprise Segment
        7.11.3 性能优化

第三部分 通过重构来加深理解
    (1) 复杂巧妙的领域模型是可以实现的,也是值得我们去花费力气实现的。
    (2) 这样的模型离开不断的重构是很难开发出来的,重构需要领域专家和热爱学习领域知识的开发人员密切参与进来。
    (3) 要实现并有效地运用模型,需要精通设计技巧。

    重构的层次
        重构就是在不改变软件功能的前提下重新设计它
    深层模型
        深层模型能够穿过领域表象,清楚地表达出领域专家们的主要关注点以及最相关的知识
    深层模型/柔性设计
    发现过程

第8章 突破
    8.1 一个关于突破的故事
        8.1.1 华而不实的模型
        8.1.2 突破
        8.1.3 更深层模型
        8.1.4 冷静决策
        8.1.5 成果
    8.2 机遇
    8.3 关注根本
    8.4 后记:越来越多的新理解
第9章 将隐式概念转变为显示概念
    9.1 概念挖掘
        9.1.1 倾听语言
            倾听领域专家使用的语言。有没有一些术语能够简洁地表达出复杂的概念?
            他们有没有纠正过你的用词(也许是很委婉的提醒)?
            当你使用某个特定词语时,他们脸上是否已经不再流露出迷惑的表情?
            这些都暗示了某个概念也许可以改进模型
        9.1.2 检查不足之处
        9.1.3 思考矛盾之处
        9.1.4 查阅书籍
        9.1.5 尝试,再尝试
    9.2 如何为那些不太明显的概念建模
        9.2.1 显式的约束
        9.2.2 将过程建模为领域对象
        9.2.3 模式: speicication
        9.2.4 speicication 的应用和实现
            speicication 最有价值的地方在于它可以将看起来完全不同的应用功能统一起来
                1. 验证对象,检查它是否能满足某些需求或者是否已经为实现某个目标做好了准备
                2. 从集合中选择一个对象
                3. 指定在创建新对象时必须满足某种需求
第10章 柔性设计
        为了使项目能够随着开发工作的进行加速前进,而不会由于它自己的老化停滞不前,设计必须要让人们乐于使用,而且易于做出修改。这就是柔性设计(supple design)
    10.1 模式: intention-revealing(意图揭示) interfaces
        如果开发人员为了使用一个组件而必须要去研究它的实现,那么就失去了封装的价值。
        当某个人开发的对象或操作被别人使用时,如果使用这个组件的新的开发者不得不根据其实现来推测其用途,那么他推测出来的可能并不是那个操作或类的主要用途。
        如果这不是那个组件的用途,虽然代码暂时可以工作,但设计的概念基础已经被误用了,两位开发人员的意图也是背道而驰。

        在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的的。
        这样可以使客户开发人员不必去理解内部细节。这些名称应该与UBIQUITOUS LANGUAGE保持一致,以便团队成员可以迅速推断出它们的意义。
        在创建一个行为之前先为它编写一个测试,这样 
    10.2 模式: side-effect-free function
        多个规则的相互作用或计算的组合所产生的结果是很难预测的。
        开发人员在调用一个操作时,为了预测操作的结果,必须理解它的实现以及它所调用的其他方法的实现。
        如果开发人员不得不“揭开接口的面纱”,那么接口的抽象作用就受到了限制。
        如果没有了可以安全地预见到结果的抽象,开发人员就必须限制“组合爆炸”,这就限制了系统行为的丰富性。

        尽可能把程序的逻辑放到函数中,因为函数是只返回结果而不产生明显副作用的操作。
        严格地把命令(引起明显的状态改变的方法)隔离到不返回领域信息的、非常简单的操作中。
        当发现了一个非常适合承担复杂逻辑职责的概念时,就可以把这个复杂逻辑移到VALUE OBJECT中,这样可以进一步控制副作用
    10.3 模式: assertion
        如果操作的副作用仅仅是由它们的实现隐式定义的,那么在一个具有大量相互调用关系的系统中,起因和结果会变得一团糟。
        理解程序的唯一方式就是沿着分支路径来跟踪程序的执行。封装完全失去了价值。跟踪具体的执行也使抽象失去了意义
    10.4 模式: conceptual contour(轮廓)
    10.5 模式: standalone class
    10.6 模式: closure of operation
    10.7 声明式设计
        领域特定语言
    10.8 声明式设计风格
        用声明式的风格来扩展SPECIFICATION
    10.9 切入问题的角度
        10.9.1 分割子领域
        10.9.2 尽可能利用已有的形式
第11章 应用分析模式
第12章 将设计模式应用于模型
    12.1 模式: strategy (也称为policy)
        定义了一组算法,将每个算法封装起来,并使它们可以互换。STRATEGY允许算法独立于使用它的客户而变化
    12.2 模式: composite
        将对象组织为树来表示部分—整体的层次结构。利用COMPOSITE,客户可以对单独的对象和对象组合进行同样的处理
    12.3 为什么没有介绍 flyweight
第13章 通过重构得到更深层的理解
        (1) 以领域为本
        (2) 用一种不同的方式来看待事物
        (3) 始终坚持与领域专家对话
    13.1 开始重构
    13.2 探索团队
    13.3 借鉴先前的经验
    13.4 针对开发人员的设计
        软件不仅仅是为用户提供的,也是为开发人员提供的
    13.5 重构的时机
        如果一直等到完全证明了修改的合理性之后才去修改,那么可能要等待太长时间了
        人们虽然看到了修改代码会有风险,还要花费开发时间,但却不容易看到维持一个拙劣设计也有风险,而且迁就这种设计也要付出代价
    13.6 危机就是机遇

第四部分 战略设计

第14章 保持模型的完整性
    14.1 模式: bounded context
        任何大型项目都会存在多个模型。
        而当基于不同模型的代码被组合到一起后,软件就会出现 bug、变得不可靠和难以理解。
        团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用

        明确地定义模型所应用的上下文。
        根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设臵模型的边界。
        在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆
    14.2 模式: continuous integration
        当很多人在同一个BOUNDED CONTEXT中工作时,模型很容易发生分裂。
        团队越大,问题就越大,但即使是3、4个人的团队也有可能会遇到严重的问题。
        然而,如果将系统分解为更小的 CONTEXT,最终又难以保持集成度和一致性

        建立一个把所有代码和其他实现工件频繁地合并到一起的过程,并通过自动化测试来快速查明模型的分裂问题。
        严格坚持使用UBIQUITOUS LANGUAGE,以便在不同人的头脑中演变出不同的概念时,使所有人对模型都能达成一个共识
    14.3 模式: context map
        其他团队中的人员并不是十分清楚CONTEXT的边界,他们会不知不觉地做出一些更改,从而使边界变得模糊或者使互连变得复杂。
        当不同的上下文必须互相连接时,它们可能会互相重叠

        识别在项目中起作用的每个模型,并定义其BOUNDED CONTEXT。这包括非面向对象子系统的隐含模型。
        为每个BOUNDED CONTEXT命名,并把名称添加到UBIQUITOUS LANGUAGE中。
        描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。 
        先将当前的情况描绘出来。以后再做改变

        14.3.1 测试 context 的边界
        14.3.2 context map 的组织和文档化
    14.4 bounded context 之间的关系
    14.5 模式: shared kernel
        当不同团队开发一些紧密相关的应用程序时,如果团队之间不进行协调,即使短时间内能够取得快速进展,但他们开发出的产品可能无法结合到一起。
        最后可能不得不耗费大量精力在转换层上,并且频繁地进行改动,不如一开始就使用CONTINUOUS INTEGRATION那么省心省力,
        同时这也造成重复工作,并且无法实现公共的UBIQUITOUS LANGUAGE所带来的好处

        从领域模型中选出两个团队都同意共享的一个子集。当然,除了这个模型子集以外,还包括与该模型部分相关的代码子集,或数据库设计的子集。
        这部分明确共享的内容具有特殊的地位,一个团队在没与另一个团队商量的情况下不应擅自更改它。 
        功能系统要经常进行集成,但集成的频率应该比团队中CONTINUOUS INTEGRATION的频率低一些。
        在进行这些集成的时候,两个团队都要运行测试
    14.6 模式: customer / supplier development team
        如果下游团队对变更具有否决权,或请求变更的程序太复杂,那么上游团队的开发自由度就会受到限制。
        由于担心破坏下游系统,上游团队甚至会受到抑制。
        同时,由于上游团队掌握优先权,下游团队有时也会无能为力

        在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。
        根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道双方的约定和进度。
        两个团队共同开发自动化验收测试,用来验证预期的接口。
        把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。
        这些测试使上游团队在做出修改时不必担心对下游团队产生副作用
    14.7 模式: confrmist
        当两个开发团队具有上/下游关系时,如果上游团队没有动力来满足下游团队的需求,那么下游团队将无能为力。
        出于利他主义的考虑,上游开发人员可能会做出承诺,但他们可能不会履行承诺。
        下游团队出于良好的意愿会相信这些承诺,从而根据一些永远不会实现的特性来制定计划。
        下游项目只能被搁臵,直到团队最终学会利用现有条件自力更生为止。
        下游团队不会得到根据他们的需求而量身定做的接口

        通过严格遵从上游团队的模型,可以消除在BOUNDED CONTEXT之间进行转换的复杂性。
        尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择CONFORMITY 模式可以极大地简化集成。
        此外,这样还可以与供应商团队共享UBIQUITOUS LANGUAGE。
        供应商处于统治地位,因此最好使沟通变容易。他们从利他主义的角度出发,会与你分享信息
    14.8 模式: anticorruption layer 
        当正在构建的新系统与另一个系统的接口很大时,为了克服连接两个模型而带来的困难,
        新模型所表达的意图可能会被完全改变,最终导致它被修改得像是另一个系统的模型了(以一种特定的风格)。
        遗留系统的模型通常很弱。即使对于那些模型开发得很好的例外情况,它们可能也不符合当前项目的需要。
        然而,集成遗留系统仍然具有很大的价值,而且有时还是绝对必要的


        创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。
        这个层通过另一个系统现有接口与其进行对话,而只需对那个系统作出很少的修改,甚至无需修改。
        在内部,这个层在两个模型之间进行必要的双向转换

        14.8.1 设计 anticorruption layer 的接口
        14.8.2 实现 anticorruption layer
            facade
            adapter
        14.8.3 一个关于防御的故事
    14.9 模式: separate way
        集成总是代价高昂,而有时获益却很小
        声明一个与其他上下文毫无关联的BOUNDED CONTEXT,使开发人员能够在这个小范围内找到简单、专用的解决方案
    14.10 模式: open host Service
        当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。
        需要维护的东西会越来越多,而且进行修改的时候担心的事情也会越来越多
    14.11 模式: published language
        与现有领域模型进行直接的转换可能不是一种好的解决方案。这些模型可能过于复杂或设计得较差。它们可能没有被很好地文档化。
        如果把其中一个模型作为数据交换语言,它实质上就被固定住了,而无法满足新的开发需求

        把一个良好文档化的、能够表达出所需领域信息的共享语言作为公共的通信媒介,必要时在其他信息与该语言之间进行转换
    14.12 “大象”的统一
    14.13 选择你的模型上下文策略
        14.13.1 团队决策或更高层决策
        14.13.2 置身上下文中
        14.13.3 转换边界
        14.13.4 接受那些我们无法更改的事物:描述外部系统
        14.13.5 与外部系统的关系
        14.13.6 设计中的系统
        14.13.7 用不同模型满足特殊需要
        14.13.8 部署
        14.13.9 权衡
            ^ 
            |对
            |所
            |有
            |相
            |关
            |系                                                             single bounded context
            |统
            |的
            |控
            |制                                                 shared kernel
            |能                     customer/supplier team
            |力
            |                                                   open host service 
            |
            |
            |       anticorruption layer
            |       
            |               confrmist
            |  
            |  separate ways  
             ------------------------------------------------------------------------->团队的沟通承诺/能力
        14.13.10 当项目正在进行时
    14.14 转换
        14.14.1 合并 context : separate ways -> shared kernel
        14.14.2 合并 context : shared kernel -> continuous integration
        14.14.3 逐步淘汰遗留系统
        14.14.4 open host service -> published language

第15章 精炼
    领域模型的战略精炼包括以下部分
        (1) 帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作;
        (2) 找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通; 
        (3) 指导重构;
        (4) 专注于模型中最有价值的那部分;
        (5) 指导外包、现成组件的使用以及任务委派
    15.1 模式: core domain
        在设计大型系统时,有非常多的组成部分——它们都很复杂而且对开发的成功也至关重要,
        但这导致真正的业务资产——领域模型最为精华的部分——被掩盖和忽略了

        对模型进行提炼。找到CORE DOMAIN并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。
        最有价值和最专业的概念要轮廓分明。尽量压缩CORE DOMAIN。
        让最有才能的人来开发CORE DOMAIN,并据此要求进行相应的招聘。
        在CORE DOMAIN中努力开发能够确保实现系统蓝图的深层模型和柔性设计。
        仔细判断任何其他部分的投入,看它是否能够支持这个提炼出来的CORE

        15.1.1 选择核心
        15.1.2 工作的分配
    15.2 精炼的逐步提升
    15.3 模式: generic subdomain
        模型中有些部分除了增加复杂性以外并没有捕捉或传递任何专门的知识。
        任何外来因素都会 使CORE DOMAIN愈发的难以分辨和理解。
        模型中充斥着大量众所周知的一般原则,或者是专门的细节,这些细节并不是我们的主要关注点,而只是起到支持作用。
        然而,无论它们是多么通用的元素,它们对实现系统功能和充分表达模型都是极为重要的

        识别出那些与项目意图无关的内聚子领域。把这些子领域的通用模型提取出来,并放到单独 的MODULE中。
        任何专有的东西都不应放在这些模块中
        把它们分离出来以后,在继续开发的过程中,它们的优先级应低于CORE DOMAIN的优先级,
        并且不要分派核心开发人员来完成这些任务(因为他们很少能够从这些任务中获得领域知识)。
        此外,还可以考虑为这些GENERIC SUBDOMAIN使用现成的解决方案或“公开发布的模型” (PUBLISHED MODEL)

        选择1:现成的解决方案
        选择2:公开发布的设计或模型
        选择3:把实现外包出去
        选择4:内部实现

        15.3.1 通用不等于可重用
            重用确实会发生,但不一定总是代码重用。模型重用通常是更高级的重用
        15.3.2 项目风险管理
    15.4 模式: domain vision statement
        在项目开始时,模型通常并不存在,但是模型开发的需求是早就确定下来的重点。
        在后面的开发阶段,我们需要解释清楚系统的价值,但这并不需要深入地分析模型。
        此外,领域模型的关键方面可能跨越多个BOUNDED CONTEXT,
        而且从定义上看,无法将这些彼此不同的模型组织起来表明其共同的关注点
    15.5 模式: highlighted core
        尽管团队成员可能大体上知道核心领域是由什么构成的,但CORE DOMAIN中到底包含哪些元素,不同的人会有不同的理解,甚至同一个人在不同的时间也会有不同的理解。
        如果我们总是要不断过滤模型以便识别出关键部分,那么就会分散本应该投入到设计上的精力,而且这还需要广泛的模型知识。
        因此,CORE DOMAIN必须要很容易被分辨出来

        对代码所做的重大结构性改动是识别CORE DOMAIN的理想方式,但这些改动往往无法在短期内完成。
        事实上,如果团队的认识还不够全面,这样的重大代码修改是很难进行的

        15.5.1 精炼文档
            编写一个非常简短的文档(3~7页,每页内容不必太多),用于描述CORE DOMAIN以及CORE 元素之间的主要交互过程

            风险
                (1) 文档可能得不到维护;
                (2) 文档可能没人阅读;
                (3) 由于有多个信息来源,文档可能达不到简化复杂性的目的
        15.5.2 标明 core 
            把模型的主要存储库中的CORE DOMAIN标记出来,不用特意去阐明其角色。
            使开发人员很容易就知道什么在核心内,什么在核心外
        15.5.3 把精炼文档作为过程工具
    15.6 模式: cohesive mechanism
        计算有时会非常复杂,使设计开始变得膨胀。机械性的“如何做”大量增加,把概念性的“做什么”完全掩盖了。
        为解决问题提供算法的大量方法掩盖了那些用于表达问题的方法

        15.6.1 generic subdomain 与 cohesive mechanism 的比较
        15.6.2 mechanism 是 core domain 一部分
    15.7 通过精炼得到声明式风格
    15.8 模式: segregated(隔离) core 
        模型中的元素可能有一部分属于CORE DOMAIN,而另一部分起支持作用。
        核心元素可能与一般元素紧密耦合在一起。CORE的概念内聚性可能不是很强,看上去也不明显。
        这种混乱性和耦合关系抑制了CORE。设计人员如果无法清晰地看到最重要的关系,就会开发出脆弱的设计

        对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来,并增强CORE的内聚性,同时减少它与其他代码的耦合。
        把所有通用元素或支持性元素提取到其他对象中,并把这些对象放到其他的包中——即使这会把一些紧密耦合的元素分开

        通过重构得到SEGREGATED CORE的一般步骤如下所示。
            (1) 识别出一个CORE子领域(可能是从精炼文档中得到的)。
            (2) 把相关的类移到新的MODULE中,并根据与这些类有关的概念为模块命名。
            (3) 对代码进行重构,把那些不直接表示概念的数据和功能分离出来。把分离出来的元素放到其他包的类(可以是新的类)中。尽量把它们与概念上相关的任务放在一起,但不要为了追求 完美而浪费太长时间。把注意力放在提炼CORE子领域上,并且使CORE子领域对其他包的引用变 得更明显且易于理解。
            (4) 对新的SEGREGATED CORE MODULE进行重构,使其中的关系和交互变得更简单、表达得更 清楚,并且最大限度地减少并澄清它与其他MODULE的关系(这将是一个持续进行的重构目标)。
            (5) 对另一个CORE子领域重复这个过程,直到完成SEGREGATED CORE的工作
        
        15.8.1 创建 segregated core 的代价
        15.8.2 不断发展演变的团队决策
    15.9 模式: abstract core
    15.10 深层模型精炼
        尽管任何带来深层模型的突破都有价值,但只有CORE DOMAIN中的突破才能改变整个项目的轨道
    15.11 选择重构目标
第16章 大型结构
    在一个大的系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式(这些模式被应用于整个设计)中的角色来解释这些元素,
    那么开发人员就会陷入“只见树木,不见森林” 的境地

    “大型结构”是一种语言,人们可以用它来从大局上讨论和理解系统

    设计一种应用于整个系统的规则(或角色和关系)模式,
    使人们可以通过它在一定程度上了解各个部分在整体中所处的位臵(即使是在不知道各个部分的详细职责的情况下)

    16.1 模式: evolving order
        一个没有任何规则的随意设计会产生一些无法理解整体含义且很难维护的系统。
        但架构中早期的设计假设又会使项目变得束手束脚,而且会极大地限制应用程序中某些特定部分的开发人员/设计人员的能力。
        很快,开发人员就会为适应结构而不得不在应用程序的开发上委曲求全,要么就是完全推翻架构而又回到没有协调的开发老路上来

        让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。
        不要依此过分限制详细的设计和模型决策,这些决策和模型决策必须在掌握了详细知识之后才能确定
    16.2 模式: system metaphor
        软件设计往往非常抽象且难于掌握。开发人员和用户都需要一些切实可行的方式来理解系统,并共享系统的一个整体视图

        当系统的一个具体类比正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构。
        围绕这个隐喻来组织设计,并把它吸收到UBIQUITOUS LANGUAGE中。
        SYSTEM METAPHOR应该既能促进系统的交流,又能指导系统的开发。
        它可以增加系统不同部分之间的一致性,甚至可以跨越不同的BOUNDED CONTEXT。
        但所有隐喻都不是完全精确的,因此应不断检查隐喻是否过度或不恰当,当发现它起到妨碍作用时,要随时准备放弃它
    16.3 模式: responsibility layer
        如果每个对象的职责都是人为分配的,将没有统一的指导原则和一致性,也无法把领域作为一个整体来处理。
        为了保持大模型的一致,有必要在职责分配上实施一定的结构化控制

        注意观察模型中的概念依赖性,以及领域中不同部分的变化频率和变化的原因。
        如果在领域中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。
        这些职责应该描述系统的高层目的和设计。
        对模型进行重构,使得每个领域对象、AGGREGATE和MODULE的职责都清晰地位于一个职责层当中
    16.4 模式: knowledge level
        如果在一个应用程序中,ENTITY的角色和它们之间的关系在不同的情况下有很大变化,那么复杂性会显著增加。
        在这种情况下,无论是一般的模型还是高度定制的模型,都无法满足用户的需求。
        为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性。
        具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则
    16.5 模式: pluggable component framework
        在深入理解和反复精炼基础上得到的成熟模型中,会出现很多机会。
        通常只有在同一个领域中实现了多个应用程序之后,才有机会使用PLUGGABLE COMPONENT FRAMEWORK(可插入式组件框架)
    16.6 结构应该有一种什么样的约束
    16.7 通过重构得到更适当的结构
        16.7.1 最小化
        16.7.2 沟通和自律
        16.7.3 通过重构得到柔性设计
        16.7.4 通过精炼可以减轻负担
第17章 领域驱动设计的综合运用
    17.1 把大型结构与 bounded context 结合起来使用
    17.2 将大型结构与精炼结合起来使用
    17.3 首先评估
        (1) 画出CONTEXT MAP
        (2) 注意项目上的语言使用
        (3) 理解重点所在
        (4) 项目所采用的技术是遵循MODEL-DRIVEN DESIGN,还是与之相悖?
        (5) 团队开发人员是否具备必要的技能?
        (6) 开发人员是否了解领域知识?他们对领域是否感兴趣?
    17.4 由谁制定策略
        17.4.1 从应用程序开发自动得出的结构
        17.4.2 以客户为中心的架构团队
    17.5 制定战略设计决策的6个要点
        决策必须传达到整个团队
        决策过程必须收集反馈意见
        计划必须允许演变
        架构团队不必把所有最好、最聪明的人员都吸收进来
        战略设计需要遵守简约和谦逊的原则
        对象的职责要专一,而开发人员应该是多面手

        17.5.1 技术框架同样如此
        17.5.2 注意总体规划
结束语
    理解目标领域并将学到的知识融合到软件中
    
    虽然有才能的软件工程师通常都认为纯粹的技术任务是最有趣、最有挑战性的,但领域驱动设计展现了一个同样富有挑战性(甚至具有更大挑战性)的新领域

Shellbye avatar May 17 '19 04:05 Shellbye