整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

如何从0到1实践DDD

辑导语:DDD(Domain-driven design,领域驱动设计)是一种架构设计方法论,通过边界划分,将复杂业务领域简单化,帮助我们设计出清晰的领域和应用边界,保证业务模型与代码模型的一致性。本文作者结合实际经验,介绍了如何从0到1实践DDD,一起来看看吧。

随着业务的不断发展,我们发现自己的系统开始变得有点臃肿,为了减少复杂性,我们尝试借助DDD来改善我们的系统。本文记录了自己对DDD的理解和实践过程,欢迎大家一起探讨。见识所限,难免有理解不到位,希望路过的大佬不吝赐教。

一、为什么需要DDD

  • 当朋友和你聊工作时,你能否一语中的,说清你在开发中的业务内容及其价值?
  • 当产品和你聊需求时,你是否遇到过反复沟通之后才发现讲的不是同个东西的情况?
  • 当你在做需求评估时,你是否经常发现一个小的需求改动,总是牵一发动全身?
  • 当你在快乐写代码时,你是否经常觉得有些类可有可无,有些接口望文不知义?

如果你有以上的一些疑问,那你可以试试领域驱动设计:

DDD(Domain-driven design,领域驱动设计)是一种架构设计方法论,通过边界划分,将复杂业务领域简单化,帮助我们设计出清晰的领域和应用边界,保证业务模型与代码模型的一致性。

在细看这个定义之前,我们可以思考一下,为什么我们的业务系统会慢慢变得复杂?

常见的情况是,业务在发展过程中为了探寻发力点,需要不断地试错迭代,调整方向,而系统在设计之初,难以预期到后面的瞬息万变,为了应付业务,修修改改,久之,系统也变得复杂起来。

可以怎么办呢?及时重构呗——不改变软件系统外部行为的前提下,改善它的内部结构。

然而重构是从技术层面上抽炼出来的模型,往往不具有实际的业务含义,其他同学可能难以自然地将业务问题映射到对应的设计模型。另外,如果不能如实映射业务模型,随着业务方向调整,代码可能又开始腐败……有点像芝诺悖论中,阿基里斯永远追不上小乌龟。

那DDD怎么搞?

DDD是这么想的:”将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构”。可能大家平时有这样的想法,但是比较模糊,未形成体系,而DDD就提供了一套完整的方法论。从业务角度去审视我们的系统,从而实现高内聚低耦合的代码。

整体而言,领域驱动设计包括战略建模和战术建模: 战略设计侧重于高层次、宏观上去划分和集成限界上下文,而战术设计则关注更具体使用建模工具来细化上下文。

二、 如何实现DDD之战略建模

1. 基本概念

1)领域、子域

在讨论问题之前,我们需要先定义好问题。

领域即问题域,通常是根据一个组织所处的行业进行识别,它基于业务的愿景,定义了系统要解决的现实问题的目标和范围。领域越大,业务的范围也越大,大的领域可以拆分成小的问题域,称之为子域。根据子域重要性和功能属性划,可以将其分为三类。

核心域、支撑域和通用域:

  • 核心域:决定产品核心竞争力的子域
  • 支撑域:实现核心域目标所需的,但重要程度不如核心域的子域,一般具备强烈的个性化需求
  • 通用域:具有通用功能,可被多个子域使用的的是通用域。该子域所解决的问题一般是业界常见问题,有成熟的解决方案,可直接购买或简单修改来使用

这个几个概念其实很容易理解,不过在划分的时候,注意要从业务的视角,而不是技术功能模块来划分。

2)限界上下文

我们语言博大精深,同样的话在不同语境下就可演变出不同含义,这在沟通时总是带来不必要的麻烦。为了准确地沟通,我们需要统一语言的边界,在相同的语言边界内沟通,才不容易出差错。

一则阿凡提当理发师惩罚一个狡猾牧师的趣事:理发时,阿凡提刮脸时问牧师:“牧师,是否要眉毛?”牧师答:“这还用问,眉毛岂能不要?”.“好,你要我就给你!”,说着就把牧师的眉毛刮下来递到他手里,牧师气得说不出话来,谁叫自己说要呢。阿凡提又问:“牧师,胡子要吗?”.“不要,不要!”牧师连忙说。“好,你不要就不要。” 嗖嗖几刀就把牧师的胡子刮下来。

在一个系统中,一个名词在不同语境可能有不同的含义,我们对它关注的属性和行为也有所不同。例如,在电商系统中,对于产品Product, 在采购上下文,需要关注产品的进价、最小起订量与供货周期;在市场上下文中,则关心产品的品质、售价,以及用于促销的精美图片和销售类型;在仓储上下文中,仓库工作人员更关心产品放在仓库的哪个位置,产品的重量与体积,是否易碎品以及订购产品的数量。

限界上下文在《实现领域驱动设计》中,用了很大篇幅去讲,它有几个重要的意义:

  1. 限界上下文是领域概念的语言边界与业务边界:在这个边界内,领域概念的内涵是清晰、无歧义的
  2. 限界上下文是团队的工作边界:组织边界与限界上下文对齐
  3. 限界上下文是技术方案的实施边界:在这个边界内,技术方案是独立自治的,业务逻辑不会落入不同技术边界的间隙

经过战略建模之后,我们可以得到以下的一个模型:

2. 业务实践

为了更好地理解,我们对手上的一个项目:“IoT设备增值产品管理系统”进行实践。该项目中,我们提供给商户在IoT设备上管理增值运营产品的能力。这里的IoT设备主要是微信支付刷脸设备等。商户可以在系统中创建我们业务中的增值运营产品,如电子海报、互动海报等,创建完之后,相关的增值产品会被投放到IoT设备上,进行展示、运作:

一开始我们从业务的用例出发,认为我们的系统主要是商户在我们页面网站使用,以及IoT设备通过接口连接我们后台服务,认为这两个分属不同的子域,然后梳理了一些支撑的功能:

画完草图之后,感觉不是很确定,于是便去咨询部门的DDD专家王老师(十分感谢王立老师的指导),得到了一些宝贵的建议:我们应该避免直接从表现层去看业务,表现层就像是冰山露在水面上的棱角,这些棱角看起来毫不相干,但是实际上底层是连成一块的,这些才是我们需要关注的。

就像这个项目,表面上商户和设备是分开的,实际上它们在操作都是我们的增值运营产品,应该看成我们的系统提供统一对外的服务,然后商户和设备来使用我们的服务。UGC内容存储业务用例其实没有涉及到的,属于实现时候的东西。一番建议让我们理清了思路,于是重新梳理,得到以下的战略建模图:

整体而言,我们将整体系统梳理为8个子域:

  1. 增值运营服务子域:核心域,是我们业务主要竞争力。从业务上来讲,我们的核心是通过提供业务中IoT设备上的增值运营服务
  2. 增值运营产品子域:支撑域,这里主要是我们提供增值运营产品,如电子海报、互动海报等
  3. 生效场景子域:支撑域,业务中增值运营产品有不同生效场景,这里统一进行管理
  4. 准入子域:支撑域,现主要是业务中对使用者的一些限制规则
  5. 权限管理子域:支撑域,基于角色来管理使用者的权限
  6. 商户信息子域:支撑域,提供商户的信息
  7. IoT设备信息子域:支撑域,提供IoT设备的信息
  8. 风险识别子域:通用域,识别业务中一些安全风险,如不合规的UGC素材等。这部分是业界常见问题,可以使用通用方案来解决,实际上我们也是接入TEG的能力来实现

其中我们系统中的商户信息依赖了微信支付商户账号信息和IoT设备铺设服务信息,这里使用防腐层进行隔离,将外部的商户信息“翻译”为我们业务中的商户信息。三、如何实现DDD之战术建模梳理清楚上下文之间的关系后,我们基本了解业务的概貌,接下来需要细化上下文,进一步完善我们的模型。这里也需要用到DDD的一些基本概念。

3. 基本概念

1)实体、值对象

实体和值对象是组成领域模型的基础单元。当一个对象由其标识(而不是属性)区分时,这种对象称为实体。如在校园教务系统中,每个账户是对应着一个学生,根据学号来唯一标识,可以认为是一个实体。传统的数据建模大多是根据数据库范式设计的,每一个数据库表对应一个实体,每一个实体的属性值用单独的一列来存储,一个实体主表会对应 N 个实体从表。

与其不同,DDD 是先构建领域模型,再将业务对象映射为持久化对象。这可能导致DDD建立出来的实体,映射到具体数据库表时,可能是1对多,多对1的关系。

如一个账户实体,有它的基本信息和权限角色信息,可能就对应了2个持久化对象。另一方面,有时候为了某些查询场景的方便,会把教师账户、学生账户等对应成一个持久化对象,就成了多对1。

通过对象属性值来识别的对象,则可以认为是一个值对象。如地址信息{“省”: “广东省”,”市”:”深圳市”},我们是通过它的属性来区分出不同的地址。值对象实际上是想把一些不变的属性组合起来,减少系统的复杂性。在设计值对象的时候,需要满足以下的特性:

  1. 值对象相等性:可以通过对其属性的比较,来区分不同的值对象
  2. 不变性:需要保证值对象创建后就不能被修改,即不允许外部再修改其属性
  3. 可替换性:值对象是一个整体,当其描述的对象有变化时,需要用一个新的值对象来替换对于值对象,由于其具有不变性,且是通过属性来判断相等的,在设计对应的数据库持久化对象时,可以将其以JSON形式存储在数据库表的某一字段中

2)聚合、聚合根

在 DDD 中,实体和值对象是基础的领域对象。实体一般对应业务对象,它具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。但是我们的一个业务流程中,一般会同时涉及多个实体、值对象的操作,这里业务逻辑紧密的实体和值对象便组合成一个聚合。

从数据层面来看,同个聚合内的数据需要保持强一致性

每一个聚合有一个聚合根实体,设置聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。聚合根可以看成是聚合的管理者,或是说handle。对内其协调实体和值对象完成业务逻辑。对外则提供通过聚合ID供其他聚合关联引用,屏蔽外部对内部实体的直接访问和修改。

建议的聚合设计原则:

  1. 在一致性边界之内确保不变性:聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性。
  2. 设计小聚合:如果聚合聚合包含过多的实体,会提高管理实体的复杂性,高频操作下容易并发冲突,降低了系统的性能。
  3. 在边界之外使用最终一致性:不同的聚合之间不要求强一致性,保证最终一致性。一次事务操作中,只修改一个聚合实例,如果需要修改多个实例,可以考虑通过异步的方式保证最终一致性。

3)领域服务

领域服务的定义:领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。当某个操作不适合放在聚合(实体)或值对像上时,最好的方式便是使用领域服务。

举个例子,在一个路线导航的项目中,“路线”可能是其中的一个实体,如果业务中有“推荐路线上相关的美食”这样一个功能,那我们会想,这个功能应该归给哪个领域对象,给“路线”实体吗?有点不合适,应该路线本身关注的是起终点,时间人物等。

此时可以将其这个功能归为领域服务,它是一个路线状态无关的服务,输入路线各个节点,来得到沿路的各种美食。当然,要注意不要过度地使用领域服务,因为这很可能导致你把实体的行为都放在里面了,实体本身都变成了一些只有getter和setter的“贫血模型”。

4)领域事件

领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。

领域事件含义很广泛,可以是业务流程的一个步骤,也可以是一个事件发生后触发的后续动作,缴费完成之后,触发短信通知;上面在设计聚合的时候,我们提到一个原则:在边界之外使用最终一致性,一次事务最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应通过领域事件,达到最终一致性。

实际上是通过事件驱动的这种异步方式,对系统进行解耦。当然,如果你觉得某两个步骤,业务流程上不允许是不一致的,那就得重新考虑将其归在同个聚合中了。

4. 业务实践

我们以增值运营服务上下文为例,根据上面的理解,结合业务实际,得到以下模型:

其中增值产品是其中的一个聚合根,通过该聚合根进行各种领域操作。海报缩略图是其中的一个领域服务,通过输入产品素材中的海报url,来得到一个海报的缩略图。

四、工程实践

传统的三层架构和DDD的分层结构:

在《领域驱动设计——软件核心复杂性的应对之道》一书中,Eric提出了这样的一种分层结构,将整个系统划分为四层:用户接口层、应用层、领域层和基础设施层。

用户接口层:用户接口层负责向用户显示信息和解释用户指令。

应用层:应用层相对来说是较“薄”的一层,主要是部署了应用服务。应用服务的实现中,它负责编排和转发下一层的领域层的接口,将要实现的功能委托给一个或多个领域对象来实现,本身只负责处理业务用例的执行顺序以及结果的拼装。

领域层:领域层是比较“厚”的一层,它包含聚合根、实体、值对象、领域服务等领域模型中的领域对象,实现了核心的业务逻辑。领域层和应用层的职责看起来有点模糊。

个人觉得,可以理解是应用层描述了一个具体操作从开始到结束的每一个环节,而领域层则是对其的细化,用来处理具体的某一个环节。

比如,比如线上购物中,购物车结算这一场景可看成是一个应用行为。而这个行为又主要包括金额计算、支付、生成订单,这些子环节就可以理解为一个领域层的服务了。

基础设施层:可以看到上面三层都有箭头指向基础设施层,它的作用就是为其它各层提供通用的技术和基础服务,如数据持久化、消息中间件等DDD 分层架构中的要素与传统三层架构(用户界面层、业务逻辑层、数据访问层)还是挺相似的,一个主要的变化是将业务逻辑层的服务拆分到了应用层和领域层。应用层响应业务用例的变化,领域层关注不变的领域模型。

图片来自极客时间

《DDD实战课》在实际的代码工程便是按照这样的目录来划分,最近部门在推的整洁Git,也是这样划分目录:

接下来,便是将领域对象映射到实际的类,实现对应的属性和行为。当然,具体实现中有很多范式可参考和讨论,我们也在摸索中,待后续慢慢补充……

五、总结

DDD首先不是关于技术的,而是关于讨论、聆听、理解、发现业务价值的。——Vaughn Vernon《实现领域驱动设计》

如Vernon所说的,DDD首先是关注业务的价值的。一开始我们对业务的边界、目标可能有个大概了解,但是见解还是不尽相同。

通过一起对业务的讨论与思考,我们了解了业务的概貌及核心,明确价值所在。关注到了核心,自然可以帮助我们实现与业务契合的系统。通过这次学习与实践,我们进一步接触了DDD。

当然,这也还只是开始,更多的关联知识还隐藏在冰山之下。同时我们也明白,DDD也只是一种方法论上的参考,不是“银弹”,需要不断地去实践与思考,才能体会出它的价值。

参考:

  • Eric Evans.领域驱动设计.赵俐 盛海艳 刘霞等译.人民邮电出版社,2016.
  • 美团技术团队.领域驱动设计在互联网业务开发中的实践:https://tech.meituan.com/2017/12/22/ddd-in-practice.html?spm=a2c4e.10696291.0.0.428119a4uu9Gpl
  • 极客时间.DDD实战课:https://time.geekbang.org/column/article/152677

作者:bryanzhao,微信支付后台开发工程师

本文由 @腾讯大讲堂 原创发布于人人都是产品经理,未经许可,禁止转载。

题图来自 Pixabay,基于CC0协议。

们为什么需要领域驱动设计

在说什么是领域驱动设计之前,我觉得需要先说一下我们为什么需要领域驱动,我个人认为领域驱动设计对于研发来说改进点主要有下面三个:

  1. 从大泥球风格中解脱出来,控制代码复杂度
  2. 回归面向对象编程本质,而不是面向过程编程
  3. 专注于业务,实现不同业务领域解偶

什么是领域驱动设计

领域驱动设计(DDD)是一种软件设计思路,领域指的是业务领域,比如银行业务领域,医药销售领域;不同于传统以数据表为中心的建模方式,它以业务领域为中心来建模,能促使我们以正确的方式使用面向对象,建立饱满的领域对象。

在进行领域建模和开发时我们主要需要关注以下几点:

  1. 和领域专家一起构建一套通用语言,团队成员使用通用业务语言进行交流;
  2. 关注领域的战略设计,DDD的战略设计从更高层次抽象系统,划分出不同的系统和业务关注点,战略设计包含:领域/子域划分、通用语言的构建、限界上下文定义以及架构风格选择等;
  3. 实现DDD的战术设计,战术设计主要包含:领域对象(实体、值对象、聚合)、模块、领域服务、工厂等;

下面的思维导图是领域驱动设计需要掌握的一些点:

Tips:

1.领域驱动设计是2004年Eric Evans在《领域驱动设计:软件核心复杂性应对之道》这本书中提到的,DDD的出现是为了应对复杂业务场景下软件越来越复杂问题;也就是说通过DDD可以将软件的复杂度控制在合理的范围内。

2.领域驱动设计主要解决的是业务复杂度问题(避免大泥球风格:大泥球风格就是没有任何清楚的结构,例如随意共享的数据,随意全局化的数据结构。这样风格的系统可维护性(maintainability)和可扩展性(extensibility)都很差,最终导致整个系统难以改动,维护不下去),如果业务不复杂,则不需要使用DDD方式来处理(推荐用三层架构)。

领域驱动设计的优缺点

领域驱动设计的优缺点很明显,我这边整理了几个优缺点,供参考。

优点:

  1. 由领域专家进行领域模型设计,通过业务驱动开发而不是数据库设计来驱动项目开发,业务逻辑更加清晰;
  2. 再次强调了面向对象编程的重要性,减少面向过程编程(提高内聚性、降低上下文之间耦合);
  3. 对单元测试(自测)比较友好;

缺点:

  1. 需要有对DDD比较精通的人员进行领域的建模,如果建模不当,后续会比较麻烦;
  2. 对开发人员要求比较高,开发人员需要理解DDD才能进行开发;
  3. DDD本身概念性的东西比较多,有些概念不好理解,需要长时间的历练才能很好的掌握DDD;

如何进行领域建模

按照实现领域驱动设计一书中描述的DDD步骤主要有4步:

  1. 根据业务需求划分出初步的领域和限界上下文,以及上下文之间的关系;
  2. 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
  3. 为聚合根设计仓储,并思考实体或值对象的创建方式;
  4. 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。

划分领域和限界上下文

提炼问题域

软件开发实际作用就是为客户解决问题或者软件升级变革,所以在领域中我们划分了问题空间和解空间两个纬度空间,用于描述客户的问题(需求)和如何解决问题(解决方案)。

问题的来源:

  1. 客户痛点需求:比如电脑蓝屏、文档忘记保存等。
  2. 行业变革:移动支付、网购等。

问题域划分

首先我们需要对问题域进行拆分,拆分的理念就是由大化小,逐个击破。拆分完毕问题空间,我们需要做到逐一击破,为每一个问题形成一套落地解决方案。一个问题对应一个答案,解空间与问题空间基本上应该是一一对应的。

构建限界上下文

我们给出的解方案是一个个限界上下文,一个限界上下文负责应对其对应的问题进行解决,承载了该问题中的业务知识及规则。限界上下文是解决某一类问题的单一功能模块,例如订单限界上下文,处理开单、通知订单状态等一切与订单关联的功能。

随着问题的划分,将领域拆分成了不同的子域,每个子域有其独有的业务知识,而这些知识需要靠一门统一语言来描述。而不同限界上下文间的统一语言是相互独立的。可能在不同的限界上下文有同样的名词,但同样的名词在不同的限界上下文中可能表示的不是同一事物,即使是同一事物可能其在不同上下文中的关注点也不一致。

限界上下文之间的映射关系:

  • 合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。
  • 共享内核(Shared Kernel):两个上下文依赖部分共享的模型。
  • 客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。
  • 遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。
  • 防腐层(Anticorruption Layer):一个上下文通过一些适配和转换与另一个上下文交互。
  • 开放主机服务(Open Host Service):定义一种协议来让其他上下文来对本上下文进行访问。
  • 发布语言(Published Language):通常与OHS一起使用,用于定义开放主机的协议。
  • 大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。
  • 另谋他路(SeparateWay):两个完全没有任何联系的上下文。

子域类型

核心域

核心域是一款软件的所能获得的竞争优势的根本所在。所以在核心的建模和开发主要投入大量的时间和人力。

通用域

通用域是许多大型软件系统都有的子域。通用域例如像电子邮件和短信发送、OSS、MQ等。

支撑域

系统中的其他域被称为支撑域。支撑域的主要作用就是有助于支撑核心域的业务。比如:用户的注册等。

分析模型(从领域建模角度分析需求)

DDD分析建模主要有两种方法可以使用:用例分析法、四色建模法、事件风暴、领域驱动建模。

用例分析法

通过对需求进行分析构建出用例图如下所示(举例子不一定正确):

四色建模法

这里列一下标准的四色建模法(https://www.infoq.cn/article/xh-four-color-modeling/),其实还是需要多联系才行。

时标型(Moment-Interval)对象:具有可追溯性的记录运营或管理数据的时刻或时段对象,用红色表示 -> 操作

PPT(Party/Place/Thing)对象:代表参与到流程中的参与方/地点/物,用绿色表示 -> 名词

角色(Role)对象:在时标型对象与 PPT 对象(通常是参与方)之间参与的角色,用黄色表示 -> 操作角色

描述(Description)对象:对 PPT 对象的一种补充描述,用蓝色表示 -> 表述备注

领域建模并不是没有方法,而是选择太多,有用例法、四色建模、领域驱动建模、事件风暴等,人有一个特点是喜欢找最简单、最易用的方法,每个方法都有自己的特点,并没有好坏优劣之分,只有适不适合。听到一个方法就去尝试,尝试到一半就放弃,一种种方法尝试,最终就会迷失在方法选择上。

识别模型(主要构成)


构造模型(找到聚合根)


细化模型(反复优化)

对于DDD而言领域建模完成后,我们就需要对照模型进行开发,在开发之间我们需要知道领域战略设计中的几种架构风格。

  1. 名词做类、动词为方法,生成渐层次模型
  2. 将渐层次模型归纳划分,做更抽象的

DDD四层架构

DDD四层架构是一个松散分层架构(任意上层可以调用下层,但是下层不能调用上层),DDD四层架构的含义如下:

  1. 用户接口层:用户界面层,或者表现层,负责向用户显示解释用户命令(类似于传统三层架构中的Controller层);
  2. 应用层:定义软件要完成的任务,并且指挥协调领域对象进行不同的操作。该层不包含业务领域知识。这层和传统三层架构中不同点在于MVC中所有的业务逻辑都在Service层,但是DDD四层结构中将业务逻辑下层到领域层,应用层只负责定义方法,然后调用领域层进行处理。应用服务可以用于控制持久化事务和安全认证等。应用服务本身并不处理业务逻辑,但它确却是领域模型的直接客户。
  3. 领域层:领域层是系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手。
  4. 基础设施层:该层主要有两方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现;

如下是DDD四层架构图:

依赖倒置原则(DIP)

依赖倒置原则:我们要依赖不变或稳定的元素(类、模块或层),抽象不应该依赖于细节,细节应该依赖于抽象。依赖倒置原则可以改进分层架构,即底层服务应该依赖于高层组件所提供的接口。

​采用了依赖注入方式后,其实可以发现事实上已经没有分层概念了。无论高层还是底层,实际只依赖于抽象,整个分层好像被推平了,这就引入下一个架构六边形架构。

六边形架构

六边形架构是Alistair Cockburn在2005年提出的,在这种架构中,不同的客户通过“平等”的方式与系统交互。在《实现领域驱动设计》一书中,作者将六边形架构应用到领域驱动设计的实现,六边形的内部代表了application和domain层。外部代表应用的驱动逻辑、基础设施或其他应用。内部通过端口和外部系统通信,端口代表了一定协议,以API呈现。个人理解其实就是变成内层、适配层&外部资源,内层是应用层和领域层;适配层是之前的接口层和基础设施层;外部资源就是对外接口、缓存、MQ、数据库等等。六边形架构图如下所示:

​下面是对六边型架构的一个落地实践示例:

四层架构、DIP、六边形架构可以参考:

https://www.jianshu.com/p/c405aa19a049

http://it.hzqiuxm.com/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E6%88%98%E7%95%A5%E7%AF%87%EF%BC%884%EF%BC%89/#DDD-2

CQRS

在DDD设计思路中,DDD 通过领域对象之间的交互实现业务逻辑与流程,并通过分层的方式将业务逻辑剥离出来,单独进行维护,从而控制业务本身的复杂度。但是作为一个业务系统,查询的相关功能也是不可或缺的。在实现各式各样的查询功能时,往往会发现很难用领域模型来实现。假设在用户需要一个订单相关信息的查询功能,展现的是查询结果的列表。列表中的数据来自于订单、商品、品类、送货地址等多个领域对象中的某几个字段。这样的场景如果还是通过领域对象来封装就显的很麻烦,其次与领域知识也没有太紧密的关系。所以命令查询职责分离的设计架构(CQRS)是可以很好的解决上述的问题。

CQRS将系统分为两类操作:命令(command)、查询(Query)。命令则是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。而查询则和字面意思一样,即不会对数据产生变化的操作,只是按照某些条件查找数据。CQRS 的核心思想是将这两类不同的操作进行分离,然后在两个独立的服务中实现。这种独立服务可以是两个不同的应用或者同一个应用中不同的包进行隔离。同时理论上命令和查询的数据源也是不同的(实现读写分离)。

当然读写分离模式,肯定会遇到事务的问题,事务问题主要的包含两种情况:

  1. 当 command 端完成数据更新后,需要通过事件的形式通知 query 端系统,这就存在着一定的时间差,如果你的业务对于数据完整的实时性非常高,那么可能 CQRS 不一定适合你。
  2. 其次一个 command 触发的事件在 query 端可能需要更新数个数据模型,而这也是有可能失败的。一旦更新失败那么数据就会长时间的处于不一致状态,需要外部的介入。

所以从事务的角度来看 CQRS,你需要面对的是问题从根本来说是个最终一致性的问题。对于团队处理这种最终一致性问题需要有对应的方案。

CQRS架构如下所示:

​领域对象

了解了战略设计后,我们主要做具体的实施,具体实施时我们需要了解实体、值对象和聚合这几个领域对象。

实体&值对象

我们先看一下他们的定义:

实体:许多对象不是由它们的属性来定义,而是通过一系列的连续性(continuity)和标识(identity)来从根本上定义的。只要一个对象在生命周期中能够保持连续性,并且独立于它的属性(即使这些属性对系统用户非常重要),那它就是一个实体。

值对象:当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它相应的行为。我们需要将值对象看成不变对象,不要给它任何身份标识,还应该尽量避免像实体对象一样的复杂性。

对于实体Entity,实体核心是用唯一的标识符来定义,而不是通过属性来定义。即即使属性完全相同也可能是两个不同的对象。同时实体本身有状态的,实体又演进的生命周期,实体本身会体现出相关的业务行为,业务行为会实体属性或状态造成影响和改变。

如果从值对象本身无状态,不可变,并且不分配具体的标识层面来看。那么值对象可以仅仅理解为实际的Entity对象的一个属性结合而已。该值对象附属在一个实际的实体对象上面。值对象本身不存在一个独立的生命周期,也一般不会产生独立的行为。

对于实体和值对象详细说明可以参考:https://www.jianshu.com/p/da51d16dbdc4

聚合&聚合根

聚合是业务和逻辑紧密关联的实体和值对象组合而成,聚合是数据修改和持久化的基本单元,一个聚合对应一个数据的持久化;同时将选择一个实体作为每个聚合的根,并仅允许外部对象持有对聚合根的引用。作为一个整体来定义聚合的属性和不变量,并把其执行责任赋予聚合根或指定的框架机制。

https://www.cnblogs.com/laozhang-is-phi/p/9916785.html

最小化集成

在所有的DDD项目中,通常存在多个限界上下文,这意味着我们需要找到合适的方法对这些上下文进行集成。当模型概念从上游上下文流入下游上下文中时, 尽量使用值对象来表示这些概念。这样的好处是可以达到最小化集成,即可以最小化下游模型中用于管理职责的属性数目。使用不变的值对象使得我们做更少的职责假设。

贫血模型&充血模型

贫血模型:指使用的领域对象中只有setter和getter方法(POJO),所有的业务逻辑都不包含在领域对象中而是放在业务逻辑层。贫血模型中领域对象只有属性没有丰富的操作,使用时需要单独的Dao层配合使用,风格比较面向过程。

充血模型:将大多数业务逻辑和持久化放在领域对象中,业务逻辑只是完成对业务逻辑的封装、事务和权限等的处理。充血模型更加倾向于面向对象的设计风格(可以参考:https://www.jianshu.com/p/fae3da337ebc)。

模块&领域服务&工厂

模块(Module)是DDD中明确提到的一种控制限界上下文的手段,在我们的工程中,一般尽量用一个模块来表示一个领域的限界上下文。一般的工程中包的组织方式为{com.公司名.组织架构.业务.上下文.*},这样的组织结构能够明确的将一个上下文限定在包的内部。
领域服务:领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。
工厂:在DDD中有可能存在复杂对象的创建,如果使用对象本身进行创建有可能会破坏单一责任原则,所以需要工厂进行对象创建。

GML

  • 概念

SGML称为:标准通用标记语言。

定义电子文档结构和描述其内容的国际标准语言。

  • 特点

1:通用标言为语法置标提供了异常强大的工具,同时具有极好的扩展性,因此在数据分类和索引中非常有用;

2:是所有 电子文档标记语言的起源,早在万维网发明之前“通用标言”就已存在。

只描述文档标记应该是怎么样的元语言。

总结:HTML是SGML的一个实例,它的DTD作为标准被固定下来,因此,HTML不能作为定义其它置标语言的元语言。


XML

  • 概念

标记电子文件使其具有结构性的标记语言,可扩展标记语言。

  • 特点

1:结合

结合了HTML和SGML,是一种非常复杂的文档的结构。

2:友好

网站设计者可以定义自己的文档类型。

  • 用途

1:大量高度结构化数据的防卫区

2:其他各种工业领域,利于分类和索引。

下面是 小二哥 写给 小二妹 的便签,存储为 XML:

<note>

<to>小二妹</to>

<from>小二哥</from>

<heading>Reminder</heading>

<body>记得给小伙伴们发福利哦!</body>

</note>

总结:XML 被设计用来传输和存储数据,是扩展标记语言(EXtensible Markup Language),很类似 HTML。XML 的设计宗旨是传输数据,而非显示数据,标签没有被预定义。需要自行定义标签。具有自我描述性,是 W3C 的推荐标准。


DHTML

DHTML 不是 W3C 标准。指动态 HTML(Dynamic HTML)。

DHTML 不是由万维网联盟(W3C)规定的标准,是一个营销术语 - 被网景公司(Netscape)和微软公司用来描述 4.x 代浏览器应当支持的新技术。

  • 用途

DHTML 是一种用来创建动态站点的技术组合物。

总结:对大多数人来说,DHTML 意味着 HTML 4.0、样式表以及 JavaScript 的结合物,是动态的HTML。


XHTML

XHTML是更严格更纯净的 HTML 代码。是指可扩展超文本标签语言(EXtensible HyperText Markup Language)。

XHTML是当前HTML版的继承者。HTML语法要求比较松散,这样对网页编写者来说,比较方便,但对于机器来说,语言的语法越松散,处理起来就越困难,对于传统的计算机来说,还有能力兼容松散语法,但对于许多其他设备,比如手机,难度就比较大。因此产生了由DTD定义规则,语法要求更加严格的XHTML。

实例:

<!DOCTYPE Doctype goes here>

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>Title goes here</title>

</head>

<body>

</body>

</html>

总结:XHTML 的目标是取代 HTML,与 HTML 4.01 几乎是相同的, 是更严格更纯净的 HTML 版本。XHTML 是作为一种 XML 应用被重新定义的 HTML。是一个 W3C 标准。



HTML5

HTML5是下一代的 HTML。将成为 HTML、XHTML 以及 HTML DOM 的新标准。

HTML 的上一个版本诞生于 1999 年。自从那以后,Web 世界已经经历了巨变。

HTML5 仍处于完善之中。然而,大部分现代浏览器已经具备了某些 HTML5 支持。

  • 特点

1:语义特性(Class:Semantic)

2:本地存储特性(Class: OFFLINE & STORAGE)

3:设备兼容特性 (Class: DEVICE ACCESS)

4:连接特性(Class: CONNECTIVITY)

5:网页多媒体特性(Class: MULTIMEDIA)

6:三维、图形及特效特性(Class: 3D, Graphics & Effects)

一个简单的媒体播放实例

<!DOCTYPE HTML>

<html>

<body>

<video width="320" height="240" controls="controls">

<source src="kecheng.ogg" type="video/ogg">

<source src="kecheng.mp4" type="video/mp4">

你的浏览器不支持html5,请更换浏览器。

</video>

</body>

</html>

总结:至于html5和html就不用多讲了吧。HTML5在2007年被万维网联盟(W3C)新的工作组采用。这个工作组在2008年1月发布了HTML 5的首个公开草案。眼下,HTML5处于“最火”状态,HTML5 已成为 HTML、XHTML 以及 HTML DOM 的新标准。


上面分别介绍了SGML、XML、DHTML、XHTML、HTML5和HTML的之间的关系。顺便也看看HTML吧。

HTML

  • 概念

超文本标记语言,标准通用标记语言下的一个应用。

  • 特点

1:一种规范。

2:一种标准。

3:简单但是功能强大,灵活方便。

4:具有极强的扩展性。

5:不牵扯平台。

实例:

<html>

<body>

<h1>这个是标题</h1>

<p>这个是段落。</p>

</body>

</html>

总结:是用来描述网页的一种超文本标记语言 (Hyper Text Markup Language), 不是一种编程语言,而是一种标记语言 (markup language),使用标记标签来描述网页。


·欢迎留言评论哦~