美文网首页
领域驱动设计

领域驱动设计

作者: Ginger12 | 来源:发表于2017-07-27 23:36 被阅读381次

    一、何为领域驱动设计

    在启动一个软件项目时,我们应该关注软件涉及的领域。软件的最终目的是增进一个特定的领域。

    1. 如何理解你的领域
      最佳的方式是让软件成为领域的一个映射。软件需要包含领域里重要的核心概念和元素,并精确实现他们之间的关系。也就是说,软件需要对领域进行建模。
    2. 构建领域知识
      举个例子:飞机飞行控制系统
      1. 每一个飞行器有一个出发机场和目的机场


        img
      2. 飞机飞行计划


        img
      3. 路线是有一些小的区间组成,可以看做一些方位点


        img
      4. 实际上飞行员会把这些点看做地球上的映射


        img
      5. 驾驶员离开前会接到详细的飞行计划,飞行计划包括:飞行路线、巡航高度、巡航速度和飞机的类型等等


        img
      6. 我们并不对飞行器的类型颜色等感兴趣,而是对“飞行”感兴趣,“飞行”才是系统的核心概念


        img

    二、通用语言

    1. 对公共语言的需要

      在设计过程中,我们倾向于使用自己的方言,但是没有一种方言能成为一种公共的语言,因为他们都不能满足所有的需要。

      通用语言:使用模型作为语言的主干。要求团队在进行所有的交流时都使用一致的语言,在代码中也是这样。在共享知识和推敲模型时,团队会使用演讲、文字和图形。

    2. 创建通用语言
      我们可以使用文档来表现模型。每一张小图包含模型的一个子集。这些图会包含若干个类以及他们之间的关系。然后向图中添加文本,来表现图不能表现的行为和约束。
      文档与模型必须保持同步。陈旧的文档使用了错误的语言,或不能如实的反馈模型都是没什么用的。
      另外两种可用的通用语言:

      1. UML 图:在原素较少时比较有帮助,系统较大时 UML 会难以阅读
      2. 代码:代码能够完成其功能,但不一定能表达清楚其所作的事情

    三、模型驱动设计

    我们应该如何完成从模型到代码的转换?

    一个推荐的设计技术是创建分析模型,它被认为是与代码设计相互分离的、通常是
    由不同的人完成的。
    一种更好的方法是将领域建模和设计紧密关联起来。模型在构建时就考虑到软件实
    现和设计。

    1. 模型驱动设计的基本构成要素


      img
    2. 分层架构


      img

      领域驱动设计的一个通用的架构解决方案包含了4 个概念层:

      概念层    |   内容
      **用户界面/展现层**    |   负责向用户展现信息以及解释用户命令
      **应用层** |   很薄的一层,用来协调应用的活动。它不包含业务逻辑。它也不保留业务对象的状态,但它保留有应用任务的进度状态
      **领域层** |   本层包含关于领域的信息。这是业务软件的核心所在。在这里保留业务对象的状态。对业务对象和它们状态的持久化被委托给了基础设施层
      **基础设施层**   |   本层作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支持库等作用
      
    3. 实体
      有一类对象看上去好像拥有标识符,它的标识符在历经软件的各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至能够超出软件系统的生命周期。我们把这样的对象称为实体。
      当一个对象可以用其标识符而不是它的属性来区分时,可以将标识符作为在模型中该对象定义的主要部分。使类的定义保持简单并专注于生命周期的延续性和标识符。
      实体是领域模型中非常重要的对象,并且它们应该在建模过程开始时就被考虑。

    4. 值对象
      有的时候我们需要包含一个领域对象的某些属性。我们对它是哪一个对象并不感兴趣,而是只关心它所拥有的属性。用来描述领域的特定方面、并且没有标识符的一个对象,叫做值对象。
      如果值对象是可共享的,那么它们应该是不可变的。值对象应该保持很小、很简单。
      比如:

      img
      一个客户会跟其姓名、街道、城市、州相关。最好用一个单独的对象来包含这些地址信息,客户对象会包含一个对这个对象的引用。街道、城市、州应该归属于一个对象,因为它们在概念上属于一体的,而不应该作为客户对象分离的属性。
    5. 服务
      服务的3 个特征:

      1. 服务执行的操作代表了一个领域概念,这个领域概念无法自然地隶属于一个实体或者值对象。
      2. 被执行的操作涉及到领域中的其他的对象。
      3. 操作是无状态的。
        当使用服务时,保持领域层的隔离非常重要。很容易弄混属于领域层的服务和属于基础设施层的服务。当我们在设计阶段建立模型时,我们需要确保将领域层与其他的层隔离开来。
    6. 模块
      软件代码应该具有高层次的内聚性和低层次的耦合度。虽然内聚开始于类和方法级别,它也可以应用于模块级别。推荐的做法是将高关联度的类分组到一个模块,以提供尽可能大的内聚性。

      最常用到的两个内聚是通信性内聚(communicational cohesion)和功能性内聚(functional cohesion)。

    7. 聚合
      聚合是针对数据变化可以考虑成一个单元的一组关联的对象。聚合使用边界将内部和外部的对象划分开来。每个聚合都有一个根。这个根是一个实体,并且它是外部可以访问的唯一的对象。
      聚合的一个简单的例子如下图所示。客户(Customer)是聚合的根,并且其他所有的对象都是内部的。如果需要地址(Address),一个它的副本将被传递给外部对象。

      img
    8. 工厂
      为复杂对象和聚合创建实例的职责,应该转交给一个单独的对象。虽然这个对象本身在领域模型中没有职责,但它仍是领域设计的一部分。提供一个接口来封装所有复杂的组装过程,客户不需要引用正在初始化的对象所对应的具体类。将整个聚合当作一个单元来创建,强化它们的不变量。
      有时工厂是不需要的,一个简单的构造器就足够了。在如下情况下应该使用构器:

      • 构造过程并不复杂。
      • 一个对象的创建不涉及到其他对象的创建,可以将所有需要的属性传递给构造器。
      • 客户对实现很感兴趣,可能希望选择使用策略(Strategy)模式。
      • 类是特定的类型,不存在到层级,所以不用在一系列的具体实现中进行选择。
    9. 资源库

      资源库扮演了一个全局可访问对象的存储地点

      使用一个资源库,它的目的是封装所有获取对象引用所需的逻辑。领域对象不需处理基础设施,以得到领域中对其他对象的引用。只需要从资源库中获取它们,于是模型重获它应有的清晰和专注。
      需要注意的是,资源库的实现可能会非常像是基础设施,然而资源库的接口却是纯粹的领域模型。

      img

    四、面向深层理解的重构

    1. 持续重构

      重构是不改变应用的行为而重新设计代码使得它更好的过程。
      重构通常是非常谨慎的,按照小幅且可控的步骤进行。

    2. 凸现关键概念
      约束是一个很简单的表达不变量的方式。无论对象的数据如何变化,不变量都要得到保持。简单的实现方式是将不变量的逻辑放在一个约束中。
      处理过程(process)通常在代码中被表达为procedure。从我们开始使用面向对象语言后我们就不再用一种过程化的方法,所以我们需要为处理过程选择一个对象,然后给它添加行为。最好的实现过程的方式是使用服务。
      规约是用来测试一个对象是否满足特定条件的。规则应该被封装到其自身的一个对象中,这将成为客户的规约,并且被保留在领域层中。

    五、保持模型的一致性

    当存在多个团队,有不同的管理和协作时,我们会面对一系列不同的挑战。


    img
    1. 界定上下文
      主要的思想是定义模型的范围,定出它的上下文的边界,然后尽最大可能保持模型的统一。
      明确定义模型所应用的上下文,根据以下因素来明确设置边界:团队的组织结构、应用的特定部分中的惯例、物理表现(例如代码库、数据库Schema)。
    2. 持续集成
      我们需要这样一个集成的过程,以确保所有新增的元素和模型原有部分能够和谐相处,在代码中也被正确地实现。
    3. 上下文映射
      上下文映射(Context Map)是描绘不同的界定上下文和它们之间关系的一份文档。它可以是像下面所展示的一个图表(diagram),也可以是其他任何形式的文档,细节层次可以有所不同。重要的是,要让每个在项目中工作的人都能够分享并理解它。
    4. 共享内核
      img
      需要指派两个团队同意共享的领域模型子集。这个明确被共享的东西有特殊的状态,在没有咨询另一个团队之前不能做修改。
      共享内核的目的是减少重复,但是仍保持两个独立的上下文。
    5. 客户-供应商
      有的时候两个子系统之间存在特殊的关系:一个子系统严重依赖另一个。两个子系统所在的上下文是不同的,并且一个系统的处理结果被作为另外一个的输入。它们没有共享的内核,因为有这样一个内核从概念上说是错误的,或者两个子系统要共享代码在技术上不可能实现。
      这个时候,应在两个团队之间建立一个清晰的客户/供应商关系。在制定计划的过程中,让客户团队扮演和供应商团队打交道的客户角色。对满足客户需求的任务进行协商并做出预算,让每个人都理解相关的承诺和日程表。
    6. 顺从者
      客户非常依赖于供应商,然而供应商却不依赖客户,这时客户-供应商关系就不可行了。最明显的做法是将它与供应商分离开,完全自力更生。
    7. 防崩溃层


      img

      防崩溃层也许包含多个服务。每一个服务都有一个相应的Facade,对每一个Facade我们为之添加一个适配器。我们不应该为所有的服务使用一个适配器,因为这样会将很多功能混在一起,从而导致杂乱无章。
      我们必须再添加一个组件。适配器将外部系统的行为包装起来。我们还需要对象和数据转换(object and data conversion),可以使用一个转换器(translator)来完成这个任务。它可以是一个非常简单的对象,有很少的功能,满足数据转换的基本需要。如果外部系统有一个复杂的接口,最好在适配器和接口之间再添加一个额外的Facade。这会简化适配器的协议,将它和其他系统分离开来。

    8. 隔离通道
      隔离通道模式适合于以下情况:一个企业应用可由几个较小的应用组成,而且从建模的角度来看彼此之间有很少或者没有公共之处。
    9. 开放主机
      定义一个能以一组服务的形式访问你的子系统的协议。将这个协议开放出来,使得所有需要和你做集成的人都能使用它。
    10. 提炼
      即使在我们改进和创建很多抽象之后,一个大的领域还是会有一个大的模型。就是在做了很多次重构之后,模型依然会很大。对于这样的情况,就需要做一次提炼了。其思路是定义一个代表领域本质的核心域(Core Domain)。提炼过程的副产品将是包含了领域中其他部分的普通子域(Generic Subdomain)。

    初读《领域驱动设计》,回想做过的项目,感觉之前的设计还有很多不足之处在里面。希望读完可以边回顾边实践,再在文中加入一些自己的体会和感悟~~

    相关文章

      网友评论

          本文标题:领域驱动设计

          本文链接:https://www.haomeiwen.com/subject/hhotlxtx.html