暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

领域驱动设计(DDD)探索与应用

中航鲸技术 2021-11-16
667




引言:

领域驱动设计的概念是2004年Evic Evans在他的著作《Domain-Driven Design : Tackling Complexity in the Heart of Software》(领域驱动设计:软件核心复杂性应对之道)中提出的。“Domain-Driven Design领域驱动设计”简称DDD。DDD真正的从业务的角度出发,为绝大部分做纯业务的开发提供了一整套的架构思路。本文对DDD做初步的探索,希望为大家了解DDD提供一些帮助。


目录:

一、什么是DDD

二、DDD的价值

三、DDD 软件架构

四、DDD在鲸技术的实践应用

五、结束语

一、什么是DDD

DDD(Domain-Driven Design) 是一种软件设计方法,帮助我们设计高质量的软件模型。核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。DDD的目标是创造一个可测试的、可伸缩的、组织良好的软件模型。

DDD核心概念

DDD 同时提供了战略上 和战术上的建模工具来帮助我们设计高质量的软件模型。


领域模型

领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义。

限界上下文

限界上下文,一个由显示边界限定的特定职责。领域模型便存在于这个边界之内。在边界内,每一个模型概念,包括它的属性和操作,都有特殊的含义。

一个给定的业务领域会包含多个限界上下文,想与一个限界上下文沟通,则需要通过显示边界进行通信。系统通过确定的限界上下文来进行解耦,而每一个上下文内部紧密组织,职责明确,具有较高的内聚性。

一个很形象的隐喻:细胞质所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。

通用语言

在限界上下文之内的每种领域术语,词组或句子,都可以看做通用语言。它们在同一个上下文中具有唯一确定的含义,在限界上下文之外,它们可能表达不同的含义。所以,我们在确定通用语言时,需要保证它们的含义明确,不模糊。

通用语言是提炼领域知识的产出物,获得统一语言就是需求分析的过程,也是团队中各个角色就系统目标、范围与具体功能达成一致的过程。通用语言属于团队专有,跨出这个团队,理解可以完全不一样。



实体:

当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity).

例:最简单的,保险产品建立,每个保险产品都是独一无二的,都有产品ID作为唯一标识。

在实践上建议将属性的验证放到实体中。
值对象

当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)

例:比如地址信息,我们只需要知道{“province”:“上海”,“detail”:"XX区XX路XX弄"}这样的值信息就可以了,这避免了我们对标识追踪带来的系统复杂性。

值对象很重要,在习惯了使用数据库的数据建模后,很容易将所有的对象看作实体。使用值对象,可以更好的做系统优化、精简设计。

值对象就有不变性、相等性 和可替换性。

在实践中,需要保证值对象创建后就不能被修改,即不允许外部再修改其属性。在不同的上下文集成时,会出现模型概念的公用,如保险产品模型会存在于投保的各个上下文中。在投保上下文中如果你只关注投保时保险产品信息的快照,那么将保险产品对象视为值对象是很好的选择。

聚合根

Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。聚合是一个非常重要的概念,核心领域往往都需要聚合来表达。其次,聚合在技术上有非常高的价值,可以指导详细设计。聚合由根实体、值对象、和实体组成。


二、DDD的价值
应对复杂的业务

软件复杂度主要来源是需求,软件系统的需求又可以分为业务需求和技术需求。

业务复杂度跟系统的业务需求规模和需求之间的依赖关系有直接关系。系统的需求数量越大,需求之间的关系越复杂,系统的业务复杂度就越高。

技术复杂度则来自于对软件系统运行的质量要求。包括安全、高性能、高并发、高可用和高扩展性等。如系统安全性要求对访问进行控制,无论是加密还是认证授权,都需要为系统添加额外的间接层。这会增大访问延迟,还极大提升了系统代码复杂度。

技术复杂度和业务复杂度 并不是孤立的,彼此相互影响甚至相互矛盾。在一些复杂流程并要求高响应的业务场景,比如下单、秒杀等。会将一个同步的访问请求拆分为多级步骤的异步请求,再通过引入消息中间件对这些请求进行整合和分散处理。这种分离一方面增加了系统架构的复杂性,另一方面也引入了更多的资源,使得系统的高可用面临挑战,并增加了数据一致性维护的难度。

技术复杂度和业务复杂度二者混合在一起让系统的复杂度变的不可预期,难以掌控。

DDD的核心思想就是要避免业务逻辑的复杂度与技术实现的复杂度混淆在一起,确定业务逻辑与技术实现的边界。隔离各自的复杂度。

快速响应业务变化

随着微信和APP等移动互联网应用的兴起,形成了新一轮移动应用的热潮。这些APP大多面向个人,市场和需求变化快。需要系统保持快速响应能力和频繁发版的要求。企业应对变化的向应力成了成败的关键。同时在软件研发中,无论预先设计如何“精确”,总是很快发现很多坑。架构和响应能力越来越糟糕,架构腐化了。

DDD的核心是从业务出发、面向业务变化构建软件架构,实质是保证面对业务变化时我们能够有足够快的响应能力。面向业务变化而架构要求首先理解业务的核心问题,即有针对性地进行关注点分离来找到相对内聚的业务活动形成子问题域。让每个子问题的划分尽可能靠近变化的原点,子问题域内部相对稳定,未来变化频率不会很高,符合深模块特性,而子问题的边界是很容易变化的。DDD最后在实现层面利用成熟技术模式屏蔽掉技术细节的复杂度。

与微服务相辅相成

Martin Flower 提出微服务时,提出了微服务的9大架构特性,指导组织围绕业务组建团队,把业务拆分为一个个业务上高度内聚、技术上松散耦合、运行在独立进程中的小型服务,微服务架构赋予了每个服务业务上的敏捷性和技术上的自主性,因此可以针对每个服务进行独立地迭代、更新、部署和弹性扩展,从而缩短需求交付周期并加速创新。

在面对复杂业务和快速变化需求时,DDD 从业务视角进行关注点分离和应对复杂度,让业务具备更高的响应力。DDD战略设计阶段,引入限界上下文(Bounded Context)和上下文映射(ContextMap) 对问题进行合理分解,确定领域的边界以及它们之间的关系,维持模型的完整性。

限界上下文不仅限于对领域模型的控制,而在于分离关注点之后,使得整个上下文可以成为独立部署的设计单元,这就是“微服务”的概念,上下文映射的诸多模式则对应了微服务之间的协作。因此在战略设计阶段,微服务扩展了领域驱动设计的内容,反过来领域驱动设计又能够保证良好的微服务设计

边界给了实现限界上下文内部的最大自由度。这也是战略设计在分治上起到的效用,我们可以在不同的限界上下文选择不同的架构模式和技术实现,这也正好映照了微服务的特点:在技术架构上,系统模块之间充分解耦,可以自由的选择合适的技术架构,去中心化地治理技术和数据。

 


简单来说,DDD 的本质是一种软件设计方法,而微服务架构是具体的实现方式。微服务架构虽好,但是他并没有给出如何对复杂系统进行分解的具体方法论,而 DDD 正好就是解决方案。DDD 强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。

三、DDD架构设计
DDD 不需要特殊的架构,只要是能将技术问题与业务问题分离的架构即可。” 传统的三层架构分而治之、降低耦合、提高复用,但存在弊端,业务逻辑在不同层泄露,导致替换某一层变得困难、难以对核心逻辑完整测试。领域驱动设计给出了 DDD 分层架构、六边形架构、整洁架构等分层架构,它们遵循“关注点分离”原则,旨在分离和隔离业务复杂度和技术复杂度,凸显了领域模型,保证了领域模型的稳定性和一致性。

DDD分层架构

分层架构遵循了“关注点分离”原则,将属于业务逻辑的关注点放到领域层(Domain Layer)中,而将支撑业务逻辑的技术实现放到基础设施层(Infrastructure Layer)中。同时,领域驱动设计又颇具创见的引入了应用层(Application Layer)。应用层扮演了双重角色。一方面它作为业务逻辑的外观(Facade),暴露了能够体现业务用例的应用服务接口;另一方面它又是业务逻辑与技术实现的粘合剂,实现二者之间的协作。

下图展现的就是一个典型的领域驱动设计分层架构。蓝色区域的内容与业务逻辑有关,灰色区域的内容与技术实现有关,二者泾渭分明,然后汇合在应用层。应用层确定了业务逻辑与技术实现的边界,通过直接依赖或者依赖注入(DI,Dependency Injection)的方式将二者结合起来:



六边形架构(Hexagonal Architecture)

2005年Alistair Cockburn提出了六边形架构,在这个架构中,将应用分为内六边形和外六边形两层,内六边形实现应用的核心业务逻辑。外六边形完成外部应用,基础资源等的交互和访问,对于与不同的外部系统交互,由外六边形的适配器负责协议转换,保证内六边形业务逻辑的干净。

这种架构也是典型的分层架构,和DDD分层架构一样,都体现了高内聚,低耦合的设计特性。六边形也常作为指导微服务设计的重要架构之一。


CQRS(命令与查询职责分离)

CQRS 使用分离的接口将数据查询操作(Queries)和数据修改操作(Commands)分离开来,这也意味着在查询和更新过程中使用的数据模型也是不一样的,这样读和写逻辑就隔离开来了。使用 CQRS 分离了读写职责之后,可以对数据进行读写分离操作来改进性能,可扩展性和安全。DDD 和 CQRS 结合,可以分别对读和写建模。

查询模型是一种非规范化数据模型,不反映领域行为,只用于数据查询和显示。命令模型执行领域行为,在领域行为执行完成后通知查询模型。如果查询模型和领域模型共享数据源,则可以省略这一步;如果没有共享数据源,可以借助于发布订阅的消息模式通知到查询模型,从而达到数据最终一致性。对于写少读多的共享类通用数据服务(如主数据类应用)可以采用读写分离架构模式。单数据中心写入数据,通过发布订阅模式将数据副本分发到多数据中心。通过查询模型微服务,实现多数据中心数据共享和查询。


整洁架构(Clean Architecture)

整洁架构中,同心圆代表应用软件架构的不同部分,也是一种以领域模型为中心的架构,从里到外依次是 Entities、Use Cases、Interface Adapters、Frameworks and Drivers。整洁架构明确了各层的依赖关系,越往里,依赖越低,越抽象,外圆代码依赖只能指向内圆,内圆不知道外圆的任何事情。



四、DDD在鲸技术的初步应用
通用语言的定义

领域模型的价值在于提供一种通用的语言,使得领域专家、产品经理和软件技术人员联系在一起,沟通无歧义。

在各种文档和平时沟通中,保持概念统一,把概念和代码连接起来,在代码做到概念名称统一,减少混淆。鲸技术团队在需求分析、产品设计阶段首先会完成通用名词的定义和说明。


领域模型设计

鲸禧保作为互联网保险经纪公司,在上架销售互联网保险产品的时候选择了CPS对接模式。CPS对接模式中,整个投保过程在保险公司页面完成。投保完成后保险公司负责将保单信息回传给鲸选保险商城,同时保险商城会将保单同步给核心业务系统。核心业务系统根据保单信息生成经纪费等相关信息。

在对接过程中发现每家保险公司的接口对接报文协议和保单模型都不尽相同。为避免和保险公司耦合,我们采用了六边形架构【端口-适配器模式】。见下图说明:



结束语:微服务时代,软件项目业务体量与复杂性成直线上升。互联网的核心难题已经从“怎么构架大型软件”升级为“怎么保证大型软件长期高效迭代”。领域驱动设计为保证大型软件长期高效迭代提供了强大的武器


- END -





文章转载自中航鲸技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论