领域驱动设计

一、何为领域驱动设计

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

  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)。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容