在说什么是领域驱动设计之前,我觉得需要先说一下我们为什么需要领域驱动,我个人认为领域驱动设计对于研发来说改进点主要有下面三个:
领域驱动设计(DDD)是一种软件设计思路,领域指的是业务领域,比如银行业务领域,医药销售领域;不同于传统以数据表为中心的建模方式,它以业务领域为中心来建模,能促使我们以正确的方式使用面向对象,建立饱满的领域对象。
在进行领域建模和开发时我们主要需要关注以下几点:
下面的思维导图是领域驱动设计需要掌握的一些点:
Tips:
1.领域驱动设计是2004年Eric Evans在《领域驱动设计:软件核心复杂性应对之道》这本书中提到的,DDD的出现是为了应对复杂业务场景下软件越来越复杂问题;也就是说通过DDD可以将软件的复杂度控制在合理的范围内。
2.领域驱动设计主要解决的是业务复杂度问题(避免大泥球风格:大泥球风格就是没有任何清楚的结构,例如随意共享的数据,随意全局化的数据结构。这样风格的系统可维护性(maintainability)和可扩展性(extensibility)都很差,最终导致整个系统难以改动,维护不下去),如果业务不复杂,则不需要使用DDD方式来处理(推荐用三层架构)。
领域驱动设计的优缺点很明显,我这边整理了几个优缺点,供参考。
优点:
缺点:
按照实现领域驱动设计一书中描述的DDD步骤主要有4步:
软件开发实际作用就是为客户解决问题或者软件升级变革,所以在领域中我们划分了问题空间和解空间两个纬度空间,用于描述客户的问题(需求)和如何解决问题(解决方案)。
问题的来源:
首先我们需要对问题域进行拆分,拆分的理念就是由大化小,逐个击破。拆分完毕问题空间,我们需要做到逐一击破,为每一个问题形成一套落地解决方案。一个问题对应一个答案,解空间与问题空间基本上应该是一一对应的。
我们给出的解方案是一个个限界上下文,一个限界上下文负责应对其对应的问题进行解决,承载了该问题中的业务知识及规则。限界上下文是解决某一类问题的单一功能模块,例如订单限界上下文,处理开单、通知订单状态等一切与订单关联的功能。
随着问题的划分,将领域拆分成了不同的子域,每个子域有其独有的业务知识,而这些知识需要靠一门统一语言来描述。而不同限界上下文间的统一语言是相互独立的。可能在不同的限界上下文有同样的名词,但同样的名词在不同的限界上下文中可能表示的不是同一事物,即使是同一事物可能其在不同上下文中的关注点也不一致。
限界上下文之间的映射关系:
核心域是一款软件的所能获得的竞争优势的根本所在。所以在核心的建模和开发主要投入大量的时间和人力。
通用域是许多大型软件系统都有的子域。通用域例如像电子邮件和短信发送、OSS、MQ等。
系统中的其他域被称为支撑域。支撑域的主要作用就是有助于支撑核心域的业务。比如:用户的注册等。
DDD分析建模主要有两种方法可以使用:用例分析法、四色建模法、事件风暴、领域驱动建模。
通过对需求进行分析构建出用例图如下所示(举例子不一定正确):
这里列一下标准的四色建模法(https://www.infoq.cn/article/xh-four-color-modeling/),其实还是需要多联系才行。
时标型(Moment-Interval)对象:具有可追溯性的记录运营或管理数据的时刻或时段对象,用红色表示 -> 操作
PPT(Party/Place/Thing)对象:代表参与到流程中的参与方/地点/物,用绿色表示 -> 名词
角色(Role)对象:在时标型对象与 PPT 对象(通常是参与方)之间参与的角色,用黄色表示 -> 操作角色
描述(Description)对象:对 PPT 对象的一种补充描述,用蓝色表示 -> 表述备注
领域建模并不是没有方法,而是选择太多,有用例法、四色建模、领域驱动建模、事件风暴等,人有一个特点是喜欢找最简单、最易用的方法,每个方法都有自己的特点,并没有好坏优劣之分,只有适不适合。听到一个方法就去尝试,尝试到一半就放弃,一种种方法尝试,最终就会迷失在方法选择上。
对于DDD而言领域建模完成后,我们就需要对照模型进行开发,在开发之间我们需要知道领域战略设计中的几种架构风格。
DDD四层架构是一个松散分层架构(任意上层可以调用下层,但是下层不能调用上层),DDD四层架构的含义如下:
如下是DDD四层架构图:
依赖倒置原则:我们要依赖不变或稳定的元素(类、模块或层),抽象不应该依赖于细节,细节应该依赖于抽象。依赖倒置原则可以改进分层架构,即底层服务应该依赖于高层组件所提供的接口。
采用了依赖注入方式后,其实可以发现事实上已经没有分层概念了。无论高层还是底层,实际只依赖于抽象,整个分层好像被推平了,这就引入下一个架构六边形架构。
六边形架构是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
在DDD设计思路中,DDD 通过领域对象之间的交互实现业务逻辑与流程,并通过分层的方式将业务逻辑剥离出来,单独进行维护,从而控制业务本身的复杂度。但是作为一个业务系统,查询的相关功能也是不可或缺的。在实现各式各样的查询功能时,往往会发现很难用领域模型来实现。假设在用户需要一个订单相关信息的查询功能,展现的是查询结果的列表。列表中的数据来自于订单、商品、品类、送货地址等多个领域对象中的某几个字段。这样的场景如果还是通过领域对象来封装就显的很麻烦,其次与领域知识也没有太紧密的关系。所以命令查询职责分离的设计架构(CQRS)是可以很好的解决上述的问题。
CQRS将系统分为两类操作:命令(command)、查询(Query)。命令则是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。而查询则和字面意思一样,即不会对数据产生变化的操作,只是按照某些条件查找数据。CQRS 的核心思想是将这两类不同的操作进行分离,然后在两个独立的服务中实现。这种独立服务可以是两个不同的应用或者同一个应用中不同的包进行隔离。同时理论上命令和查询的数据源也是不同的(实现读写分离)。
当然读写分离模式,肯定会遇到事务的问题,事务问题主要的包含两种情况:
所以从事务的角度来看 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中有可能存在复杂对象的创建,如果使用对象本身进行创建有可能会破坏单一责任原则,所以需要工厂进行对象创建。