领域驱动设计-软件核心复杂性应对之道:第一章

发布时间 2023-04-10 22:49:18作者: LHX2018

第一部分 让领域模型发挥作用

​ 每个模型都表示人们感兴趣的某方面显示或某种想法。模型是一种简化。它是对现实的解释,并把与解决问题密切相关的方面抽象出来,而忽略无关的细节。

​ 每个软件程序的目的都是为了执行某项活动,或是满足用户的某种需求。用户会把软件程序应用于某个主题区域,这个区域就是软件的领域。

​ 为了创建真正能为用户活动创造价值的软件,开发团队必须运用一整套与这些活动有关的知识体系。所需知识的广度可能令人望而生畏,信息量和复杂度也可能超乎想象。模型正式用于解决信息超载的问题。模型是一种知识形式,它对知识进行有选择的简化和有目的的结构化。适当的模型可以使人理解信息的意义,并专注于问题相关的信息。

建模更像是制作电影:出于某种目的而概括地反映现实。并以一种特殊方式将它们展现给观众,从而讲一个故事或阐明一个观点一样。

模型在领域驱动设计中的作用

1)模型和设计的核心互相影响

2)模型是团队所有成员所使用的交流语言的中枢

3)模型是浓缩的知识

软件的核心

​ :是为用户解决领域相关的问题的能力。所有其他特性,不管有多么重要,都要服务于这个基本目的。

​ 相反,技术人才更愿意从事精细的框架工作,试图用技术来解决领域问题。他们把学习领域只是和领域建模的工作留给别人去做。软件核心的复杂性需要我们直接去面对和解决,如果不这样做,必将导致工作重点的偏离。

image
​ 模型类图UML->原型->互动->将新学到的知识融合到模型中,然后反映到软件上。领域专家也可以从原型得到具体的反馈,从而印证自己的想法。

​ 模型将很多同义词和语言描写中的微小差别做了统一,并排除了数百条与问题没有直接关系的事实,这张图相当于一个框架,开发人员可以借助于它组织新的信息来更快地学习,从而更准确地判断哪些部分重要,哪些部分不重要,并更好地与PCB工程师进行沟通。

​ 当PCB工程师提出新的功能需求时,我就让他们带我走查对象交互的场景。当模型对象无法清楚地表达某个重要场景时,我们就通过头脑风暴活动创建新的模型对象或者修改原有的模型对象,并消化理解这些模型对象中的知识。

1.1 有效的建模要素

1)模型与实现的绑定

2)获得了一种基于模型的语言。最初,工程师们不得不向我解释基本的PCB问题,而我也必须向它们解释类图的含义。但随着项目的进展,双方都能够直接使用模型中的术语,并将它们组织为复合模型结构的语句,而且无需翻译即可理解互相要表达的意思。

3)开发一个蕴含丰富知识的模型。对象具有行为和强制性规则。模型并不仅仅是一种数据模式,它还是解决复杂问题不可或缺的部分。模型包含各种类型的知识。

4)提炼模型。在模型日趋完整的过程中,重要的概念不断被添加到模型中,但同样重要的是,不再使用或不重要的概念则从模型中删除。当一个不需要的概念与一个需要的数据有关联时,则把重要的概念提取到一个新模型中,其他那些不要的概念就可以删去了。

5)头脑风暴和实验。语言和草图,再加上头脑风暴活动,将我们的讨论变成"模型实验室",在这些讨论中可以演示、尝试和判断上百种变化。当团队走查场景时,口头表达本身就可以作为所提议的模型的可行性测试,因为人们听到口头表达后,就能立即分辨出它是表达得清楚、简洁还是表达得笨拙。

​ 正是头脑风暴的创造力和大量实验才使我们找到了一个蕴含丰富知识的模型并对它进行提炼,在这个过程中,基于模型的语言提供了很大帮助,而且贯穿整个实现过程中的反馈循环也对模型起到了"训练"作用。这种知识消化将团队的知识转化为有价值的模型。

1.2 知识消化

​ 在团队所有成员一起消化理解模型的过程中,他们之间的交互也会发生变化。领域模型的不断精化迫使开发人员学习重要的业务原理,而不是机械地进行功能的开发。领域专家被迫提炼自己所知道的重要知识的过程往往也是完善其自身理解的过程,而且他们会逐渐理解软件项目所需的概念严谨性。

​ 模型在不断改进的同时,也称为组织信息流的工具。模型聚焦于需求分析。它与编程和设计紧密交互。它通过良性循环加深团队成员对领域的理解,使他们更透彻地理解模型,并对其进一步精化。模型必须对领域实用和有用。他们必须非常精确,以便使应用程序易于实现和理解。

1.3 持续学习

​ 当开始编写软件时,其实我们所知甚少。同时,所有项目都会丢失知识。已经学到了一些知识的人可能干别的事了。团队可能由于重组而被拆散,这导致知识又重新分散开。被外包出去的关键子系统可能只交回了代码,而不会将知识传递回来。而且当使用典型的设计方法时,代码和文档不会以一种有用的形式表示出这些来之不易的知识,因此一旦由于某种原因人们没有口头传递知识,那么知识就丢失了。

​ 高效率的团队需要有意识地积累知识,并持续学习。对于开发人员来说,这意味着既要完善技术知识,也要培养一般的领域建模技巧。

​ 目的是学会与与PCB专家沟通,学会理解与应用有关的主要概念,并学会检查所构建的内容是否合理。

1.4 知识丰富的设计

​ 业务活动和规则如同所涉及的实体一样,都是领域的中心,任何领域都有各种类别的概念。知识消化所产生的模型能够反映出对知识的深层理解。在模型发生改变的同时,开发人员对实现进行重构,以便反映出模型的变化,这样,新知识就被合并到应用程序中了。

​ 当我们的建模不再局限于寻找实体和值对象时我们才能充分吸取知识,因为业务规则之间可能会存在不一致。领域专家在反复研究所有规则、解决规则之间的矛盾以及修改规则使其符合常识等一系列工作中,往往不会意识到他们的思考过长有多么复杂。软件是无法完成这一工作的。正是通过与软件专家紧密协作来消化知识的过程才使得规则得以澄清和充实,并消除规则之间的矛盾以及删除一些无用规则。

示例

一艘船在一次航程中要运载的货物
image

public int makeBooking(Cargo cargo,Voyage voyage){
    int confirmation = orderConfirmationSequence.next();
    voyage.addCargo(cargo,confirmation);
    return confirmation;
}

由于总会有人临时取消订单,因此航运业的一般做法是接受比其运载能力多一些的货物。这称为"超订"。有时使用一个简单地容量百分比来表示,例如预订110%的载货量。有时则采用一些有利于主要客户或特定种类的货物的复杂规则。
image

public int makeBooking(Cargo cargo,Voyage voyage){
    double maxBooking = voyage.capacity() * 1.1;
    if( (voyage.bookedCargoSize() * cargo.size()) > maxBooking ){
        return -1;
    }
    int confirmation = orderConfirmationSequence.next();
    voyage.addCargo(cargo,confirmation);
    return confirmation;
}

现在,一条重要的业务规则被隐藏在了上面这段方法代码的一个卫语句中。

1)如果写成上面的代码那样,不可能会有业务专家能通过阅读这段代码来检验规则,即使在开发人员的帮助下也无法完成

2)不懂此业务的技术人员很难将需求文本与代码联系起来

超订规则是一个政策。政策其实一种设计模式即策略模式。
image

public int makeBooking(Cargo cargo,Voyage voyage){
    if( !overbookingPolicy.isAllowed(cargo,voyage) ){
        return -1;
    }
    int confirmation = orderConfirmationSequence.next();
    voyage.addCargo(cargo,confirmation);
    return confirmation;
}

class OverbookingPolicy{
    public boolean isAllowed(Cargo cargo,Voyage voyage){
        return (cargo.size() + voyage.bookedCargoSize()) <= (voyage.capacity() * 1.1);
    }
}

现在所有人都可以看出超订是一个独特的政策,而且超订规则的实现是明确且独立的。

现在,我不建议将这样的精细设计应用到领域的每个细节中(参考15章,如何关注重点以及如何隔离其它问题以使这些问题最小化)。

这个例子的目的是说明领域模型和相应的设计可用来保护和共享知识。

1.5 深层模型

开发人员 货物的处理:由转包商或公司的操作人员完成,包括装货、卸货、运货。

航运专家 :各部分之间存在一系列的责任传递,法律责任和执行责任的传递有一个控制过程-从托运人传递到某个本地运输商,再从这家运输商传递到另一家运输商,最后到达收货人。

​ 认识发生变化:从"集装箱在各个地点之间的运输"转变为"运货责任在各个实体之间的传递"