DDD基础
引言
<<领域驱动设计-软件核心复杂性应对之道>>:全书围绕着设计和开发实践,结合若干真实的项目案例,向读者阐述如何在真实的软件开发中应用领域驱动设计。书中给出了领域驱动设计的系统化方法,并将人们普遍接受的一些最佳实践综合到一起,融入了作者的见解和经验,展现了一些可扩展的设计最佳实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。适合各层次的面向对象软件开发人员、系统分析员阅读。 <<代码精进之路从码农到工匠>>:共有13章内容,主要分为技艺部分、思想部分和实践部分。技艺部分详细介绍了编程技巧和方法论,并配以详尽的代码案例,有助于读者提高编写代码的能力,优化代码质量。思想部分主要包括抽象能力、分治思想,以及程序员应该具备的素养等内容。实践部分主要介绍了常见的应用架构模式,以及COLA架构的设计原理。 DDD的革命性在于领域驱动设计是面向对象分析的方法论,它可以利用面向对象的特性(封装、多态)有效地化解复杂性,而传统J2EE或Spring Hibernate等事务性编程模型只关心数据。这些数据对象除了简单的setter/getter方法外,不包含任何业务逻辑,业务逻辑都是以过程式的代码写在Service中。这种方式极易上手,但随着业务的发展,系统也很容易变得混乱复杂。 学习、理解及应用领域驱动设计建议先有以下几个基础条件
- 有编程语言基础
- 面向对象设计
- 了解设计模式
- 有项目经验和架构设计经验
随着软件系统越来越庞大,需求越来越模糊,代码越来越混乱,测试越来越困难,技术演进基本不可能,而其中大型复杂的软件项目更容易走向系统老化的过程,形成需求难、开发难、测试难、创新难,单体架构局部业务膨胀可以拆成微服务,那么微服务局部业务膨胀又应该怎么做?DDD之所以火,即能解决微服务解决不了的问题。DDD是为了解决快速变化、复杂系统的设计问题。
如何解决系统老化问题使得重新崛起的DDD领域驱动设计成了业界最大的希望乃至目前阶段最理想的方式,积极践行DDD,搭建的每一个应用,实现的每一个功能,写的每一行代码,都是在精修架构思维的内功。过去系统分析和系统设计都是分离的,DDD则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。先来理解几个概念:
- POP:Procedure Oriented Programming即面向过程编程,是以功能为中心来进行思考和组织的一种编程方法,它强调的是功能(即系统的数据被加工和处理的过程),在程序设计中主要以函数或者过程为程序的基本组织方式,系统功能是由一组相关的过程和函数序列构成。从思维上来讲,面向过程更强调细节,忽视了整体性和边界性。
- OOP:Object Oriented Programming即面向对象编程,是以对象为中心,面向对象作为一种新型的程序设计方法,其是以对象模型为基础进行的抽象过程,并在应用过程中形成了描述自己的抽象概念定义。面向对象是一种编程范式,满足面向对象编程的语言,一般会提供类、封装、继承等语法和概念来辅助我们进行面向对象编程,也即是以对象作为边界。
- AOP:Aspect Oriented Programming即面向切面编程,是以通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
思想
传统架构的设计逻辑从数据库出发,先进行数据库设计、建表、字段不合理再调整,表和对象进行管线。而DDD是把数据库当成与其他组件相等的一环,可以解决现有系统因为数据库,而导致系统耦合问题。而适应快节奏的需求变更频繁的敏捷开发的出现,更是对传统架构的设计产生较大影响,架构师作为架构设计文档和图形的载体。
DDD中的设计主要指领域模型的设计。为什么是领域模型的设计而不是架构设计或其他的什么设计呢?因为DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每一个领域,都有一个对应的领域模型,领域模型能够很好的帮我们解决复杂的业务问题。领域模型设计只是整个软件设计中的很小一部分。除了领域模型设计之外,要落地一个系统,我们还有非常多的其他设计要做,比如容量规划、架构设计、数据库设计、缓存设计、框架选型、发布方案、数据迁移、同步方案、分库分表方案、回滚方案、高并发解决方案、一致性选型、性能压测方案、监控报警方案等。
一般系统设计分为概念设计、逻辑设计、物理设计三个阶段。
- 概念设计:涵盖下下1-3步
- 输出概念关联图、概念类、领域类图、填充属性的属性完备概念类
- 主要人员:架构师、产品、运营方
- 逻辑设计:
- 输出业务逻辑、业务模型、对象模型、业务用例场景、业务用例规约、可复用的业务逻辑common
- 主要人员:架构师、技术经理、技术专家、核心开发人员。
- 物理设计:
- 输出存储设计如索引、分区、分库分表
- 主要人员:设计和开发人员
定义
- DDD(Domain-Driven Design)领域驱动设计,扩大上述概念的边界(其问题域为边界),将对象组装成领域。
- DDD本质是一种软件设计思想和程序分析设计方法,一般为需求分析人员使用,不关乎具体的技术,其具体代码实现依然是以OOP和AOP为主。而架构风格又有很多,比如常见架构风格有微服务架构、SOA(面向服务的)架构、REST风格架构、CQRS架构、事件驱动架构。
- DDD是一套综合软件系统分析和设计的面向对象建模方法,领域驱动设计作为针对大型复杂业务系统的领域建模方法体系(不仅限于面向对象的领 域建模),它改变了传统软件开发工程师针对数据库建模的方式,通过面向领域的思维方式,将要解决的业务概念和业务规则等内容提炼为领域知识,然后借由不同的建模范式将这些领域知识抽象为能够反映真实世界的领域模型。
- 领域驱动设计和微服务的关系:微服务架构怎么拆、拆多小?领域驱动设计定义领域模型,从而划分领域边界,然后再根据我们的领域边界从业务的角度去进行微服务边界定义。
- 领域驱动用什么方式进行边界的定义?
- 战略设计:从业务角度,建立业务模型,划分业务的边界。
- 战术设计:根据业务模型进行技术实现,完成软件的开发和落地。
组成
领域
基本定义
一个领域本质上可以理解为一个问题域,只要是同一个领域,那问题域就相同。任何一个系统都会属于某个特定的领域,比如论坛是一个领域,只要你想做一个论坛,那这个论坛的核心业务是确定的,比如都有用户发帖、回帖等核心基本功能;传统电商领域的问题无非就是订单、商品、支付、物流、库存之类,而社交电商除了传统电商的属性外,也会附加社交、通讯相关的功能。所以同一个领域的系统都具有相同的核心业务,因为他们要解决的问题的本质是类似的。
只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。通常我们说,要成为一个领域的专家,必须要在这个领域深入研究很多年才行。因为只有你研究了很多年,你才会遇到非常多的该领域的问题,同时你解决这个领域中的问题的经验也非常丰富。很多时候,领域专家比技术专家更加吃香,比如金融领域的专家。
领域划分以事件风暴的形式(Event Storming),列出所有的用户故事(Use Story),用户故事可通过6W模型来构建,即描写场景的 Who、What、Why、Where、When 与 hoW 六个要素。然后圈选功能相近的部分,就形成了领域,领域又根据职能不同划分为:核心域、支撑域、通用域,
驱动
领域驱动领域模型设计,领域模型驱动代码实现。这个就和我们传统的数据库驱动开发的思路形成对比了。DDD中,我们总是以领域为边界,分析领域中的核心问题(核心关注点),然后设计对应的领域模型,再通过领域模型驱动代码实现。而像数据库设计、持久化技术等这些都不是DDD的核心,而是外围的东西。
领域驱动设计(DDD)的最大价值:当我们要开发一个系统时,应该尽量先把领域模型想清楚,然后再开始动手编码,这样的系统后期才会很好维护。但是,很多项目(尤其是互联网项目,为了赶工)都是一开始模型没想清楚,一上来就开始建表写代码,代码写的非常冗余,完全是过程是的思考方式,最后导致系统非常难以维护。而且更糟糕的是,出来混总是要还的,前期的领域模型设计的不好,不够抽象,如果你的系统会长期需要维护和适应业务变化,那后面你一定会遇到各种问题维护上的困难,比如数据结构设计不合理,代码到处冗余,改BUG到处引入新的BUG,新人对这种代码上手困难,等。而那时如果你再想重构模型,那要付出的代价会比一开始重新开发还要大,因为你还要考虑兼容历史的数据,数据迁移,如何平滑发布等各种头疼的问题。所以,就导致我们最后天天加班。从面向过程式的想到哪里写到哪里的思想转变为基于系统化的模型驱动的思维很难,这或许是DDD很难在中国或国外流行起来的原因吧。
设计
DDD中的设计主要指领域模型的设计,DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每一个领域,都有一个对应的领域模型,领域模型能够很好的帮我们解决复杂的业务问题,从领域和代码实现的角度来理解,领域模型绑定了领域和代码实现,确保了最终的代码实现就一定是解决了领域中的核心问题的。
子域
子域可以理解为更加细分的领域,甚至可以把子域进行更新划分,分成更多的子域。比如把电商平台看成是一个领域,那么订单、仓储、物流都可以是子域,而仓储可以划分为本地仓储、三方仓储、异地仓储等子域。子域可以划分为三种类型
- 核心子域:核心域是整个业务系统的核心,所有业务都要围绕着核心业务域展开。精炼业务域,包括领域愿景说明、突出核心、内聚机制、分离的核心、抽象核心。比如淘宝来说,淘宝属于CToC,核心是交易保证,也即是保障卖家能把货卖出去、买家能获取货品;京东BToC,核心保障口碑,注重产品质量,包括采购、仓储、物流、供应链等环境的质量。
- 通用子域:整个领域都能用到子域
- 支撑子域:不包含核心竞争力的功能,也不包含通用的功能,但又是必须支撑的。
通用语言
能够正确的、简单的、清晰的表达业务,并让项目的参与人员都能够达成共识的语言。统一语言是提炼领域知识的输出结果,也是进行后续需求迭代及重构的基础,统一语言的建立有以下几个要点:
- 统一语言必须以文档的形式提供出来,并且在整个项目组的各团队达成共识;
- 统一语言必须每个中文名有对应的英文名,并且在整个技术栈保持一致;
- 统一语言必须是完整的,包含以下要素:
- 领域模型的概念与逻辑;
- 界限上下文(Bounded Context);
- 系统隐喻;
- 职责的分层;
- 模式(patterns)与惯用法。
限界上下文
语义问题,苹果不大好吃这句话可以理解为苹果,不大好吃,也可以理解为苹果不大,好吃。限界上下文用来封装封装通用语言和领域对象,提供上线文环境,保证在领域内的一些术语、业务相关对象等有一个确切的含义、没有二义性,这个边界定义模型的使用范围。那上面例子来说,苹果不大好吃,下次再也不买了。
限界上下文包含两部分:上下文(Context)是业务目标,限界(Bounded)则是保护和隔离上下文的边界。限界上下文没有统一的划分标准,需根据自己的业务场景来甄别如何划分。一个上下文中包含了相同的领域知识,角色在上下文中完成动作目标;边界体现在以下几方面:
- 领域逻辑层:确定了领域模型的业务边界,维护了模型的完整性与一致性,从而降低系统的业务复杂度;
- 团队合作层:限界上下文一般也是用户换分团队的依据;
- 技术实现层:限界上下文可当成是微服务的划分边界;
战略设计和战术设计
战略设计是一种用高层次的视野来审视软件系统的方式,战略设计也可以出现建模的方式问题、频繁进行战略改动问题。UML建模,适合小范围。关联关系、引用关系,解决复杂问题如四色建模法、限界纸笔法、事件风暴。
- 问题空间:对问题空间进行合理分解,识别出核心子领域、通用子领域和支撑子领域, 并确定各个子领域的目标、边界和建模策略。
- 解空间:对问题空间进行解决方案的架构映射,通过划分限界上下文,为统一语言提供知 识语境,并在其边界内维护领域模型的统一。每个限界上下文的内部有着自己的架构, 限界上下文之间的协作关系则通过上下文映射来体现和表达。
- 战术设计阶段需要在限界上下文内部开展领域建模,前提是你为限界上下文选择了领域模型 模式。在限界上下文内部,需要通过分层架构将领域独立出来,在排除技术实现的干扰下,通过与 领域专家的协作在统一语言的指导下逐步获得领域模型。
- 战术设计阶段最重要的设计元模型是聚合模式。虽然聚合是实体和值对象的概念边界, 然而 在获得了清晰表达领域知识的领域模型后,我们可以将聚合视为表达领域逻辑的最小设计单元。如 果领域行为是无状态的,或者需要多个聚合的协作,又或者需要访问外部资源,则应该将它分配给 领域服务。至于领域事件,则主要用于表达领域对象状态的迁移,也可以通过事件来实现聚合乃至 限界上下文之间的状态通知。
- 从战略设计到战术设计是一个自顶向下的设计过程,体现为设计原则对设计决策的指导;将 战术设计方案反馈给战略设计,则是自底向上的演化过程,体现为对领域概念的重构引起对战略架 构的重构。二者形成不断演化、螺旋上升的设计循环。
领域模型
领域模型是对领域内的概念类或现实世界中对象的可视化表示。包括业务对象模型、业务对象之间的引用关系。
- 业务对象包含一下三种
- 业务角色:表示一个角色以及它所承担的一系列职责,比如收银员,职责计算商品价格、收钱、找零、退换货等。
- 业务实体:表示的其实就是你与业务角色交互所需要的可交付的工件、资源、事件,比如电商里的商品、发票。
- 业务用例:表示的就是我们业务角色和业务实体之间是如何执行工作流程,比如业务链路、测试用例。
- 领域建模方法
- 划分好边界上下文,通常每个子域(sub domain)对应一个边界上下文(bounded context),同一个边界上下文中的概念是明确的,没有任何歧义;
- 在每个边界上下文中设计领域模型,具体的领域模型设计方法有很多种,如以场景为出发点的四色原型分析法,这个步骤最核心的就是找出聚合根,并找出每个聚合根包含的信息,前提是先能设计聚合。
- 画出领域模型图,圈出每个模型中的聚合边界;
- 设计领域模型时,要考虑该领域模型是否满足业务规则,同时还要综合考虑技术实现等问题,比如并发问题;领域模型不是概念模型,概念模型不关注技术实现,领域模型关心;所以领域模型才能直接指导编码实现;
- 思考领域模型是如何在业务场景中发挥作用的,以及是如何参与到业务流程的每个环节的;
- 场景走查,确认领域模型是否能满足领域中的业务场景和业务流程;
- 模型持续重构、完善、精炼;
- 领域模型的核心作用:
- 抽象了领域内的核心概念,并建立概念之间的关系;
- 领域模型承担了领域内的状态的维护;
- 领域模型维护了领域内的数据之间的业务规则,数据一致性;
业务对象模型
业务对象模型就是业务逻辑流转的过程中需要的所有角色,甚至还包括你的业务逻辑流转本身。业务模型在DDD大概实现有四种方式
- 失血模型:对象中只会包含get和set方法,业务逻辑都散落在Service层里面。
- 肿胀的业务代码逻辑,难以维护。
- 无法应对频繁更改的需求。
- 领域对象结构简单
- 优点
- 缺点
- 贫血模型:可以分为固有行为和非固有行为,比如人的类,固有行为有吃饭、睡觉,非固有行为如编程、打游戏。
- 充血模型
- 职责不好划分,开发者水平要很高。
- 模型中包含了大量的操作,当去实例化的时候会增加很多不必要的消耗。
- 更加符合面向对象的原则。
- 业务逻辑层很薄,符合单一职责原则。
- 优点
- 缺点
- 涨血模型:直接就取消掉Service层
- 取消业务逻辑层,直接在Domain Object上封装事务以及授权,因此授权很多原本不属于这个领域对象的逻辑。当模型不稳定时会影响到代码稳定性和维护的稳定性。
- 简化分层架构。
- 也算是符合面向对象设计原则。
- 优点
- 缺点
失血模型和涨血模型是现在不太推荐的使用的模型,贫血模型是用的最多的,而充血模型对于程序员的要求非常高。贫血模型的对象会被各种Service调用,分布在不同业务中,对于业务理解难度加大。而充血模型每个实体操作自己实体的变化,跨实体的变化通过领域服务实现,领域服务调用实体方法完成状态改变。