当我们谈论DDD时,我们在谈论什么?这些概念各不相同,相关的概念也千差万别,但都属于DDD的范畴。我看过很多DDD的讨论和workshop,发现大家无法达成一致,往往是因为他们心中的问题不尽相同。我尝试将这些问题在软件设计领域划分为几个独立的类别,这样可以帮助我在一个明确的范围内更好地与他人讨论和交流。比较经典的方式是分为战略设计和战术设计。由于领域模型设计的复杂性较高,我将领域模型设计与战术设计分开,单独成一类进行更好的讨论。下面我分别讨论这三类的概念和方法。1.DDD战略设计在这个类别中,主要讨论的目标是复杂的业务需求。它有多复杂?可能需要多个团队协同工作,或者一个团队分阶段开发,需要设计成多个独立部署运行的服务,会有多个代码库。这个类别可以有很多名称,比如DDD策略设计、进程间架构、微服务架构设计等。这个类别讨论的主要问题是如何将这个复杂的业务需求合理的划分成多个部分,从而分而治之.为什么要拆分成多个部分?因为解决一个复杂问题的有效方法是把它分解成多个相对简单的问题,分别解决。如果不分解,这个复杂的问题往往会让我们卡在解决过程中。即使设计出了解决方案,也往往会因为解决方案的复杂性而造成团队的认知超载。1.划分法由于战略设计需要将整个业务需求划分为多个部分,如何找到划分的接缝?在业界看到了一些方法:(1)Boundedcontext在《领域驱动设计》中,Eric提出了boundedcontext。从领域模型设计的角度来看,为了保持模型的完整、独立和清晰,需要将限界上下文标识为模型的边界。书中没有完善的识别方法,更多的是提出一些概念。限界上下文常用于辅助判断接缝的正确性。在有界上下文中,领域知识相对完整。(2)CoreDomain在《领域驱动设计》中,Eric提出了refinement和coredomain。确定模型中最有价值的核心域并将它们隔离。这也不是一个完整的划分方法,因为只提到了核心域。我在Howtodividetheboundedcontextblog中提出了一种基于这种方法分解问题域的方法。(3)Eventstormworkshop事件风暴workshop可能是最早用来指导限界上下文划分的方法。将上一步(事件风暴)产生的聚合进行分组,根据业务内聚性和相关性划分边界,根据边界上下文的定义进行判断,并给出上下文名称。——【面向服务设计阶段的路径规划】但是“业务内聚性和相关性”确实不是一个很好的划分依据。EventStorm的创始人Alberto曾提出通过关键事件识别不同阶段,然后识别限界上下文的方法,这似乎是一种比较靠谱的方法。(4)8XFlow8XFlow提出了一套比较完整的划分方法。先定义“业务”和“领域”,再划分“业务”和“领域”,再根据契约将业务划分到不同的上下文中,最后完成划分。(5)现代企业架构白皮书现代企业架构白皮书提出了职责分工。流类标识不同的业务流程阶段,规范类提取业务规则,视图类专门为统计报表而存在,配置类提供配置工具。2、反思我也尝试过一些其他的划分方式,比如按时间阶段划分,按用户划分,按使用场景划分,按变化频率划分。这些方法与上面的一些方法有些相似。糟糕的分区会导致分布式单体:每次更改都必须修改多个服务,每次部署都必须同时部署多个服务,服务之间有很多通信,同一个团队管理多个服务,数据库,相同的代码和模型在服务之间共享。或许我们可以总结出一些原则来帮助我们验证划分是否合理。比如高内聚低耦合,比如服务边界清晰,可以自治,可以独立进化,比如尽可能减少对其他服务的依赖。2.DDD战术设计在战略层面划分完服务后,我们来看一个服务的内部。在这个类别中,我们主要讨论如何在服务内部划分和组织代码。与上一节类似,代码中有不同的职责;与上一节不同的是,代码层级的划分已经有了比较成熟的方法。这一类可以有很多名称,比如DDD战术设计、进程内架构、分层架构等。需要指出的是,在一个服务内部,如果领域模型足够复杂,在分离领域逻辑和技术实现细节之前,需要先按照模块来划分,再根据上面的领域逻辑和技术实现细节来划分。有关相关讨论,请参阅前缀打包与后缀打包。1.划分方法(1)《领域驱动设计》中的分层架构Eric在2003年提出的分层架构,相对于传统的表示层+业务逻辑层+数据访问层的三层架构,多了一层。主要区别在于业务逻辑层分为应用层和领域层。图片引用自《领域驱动设计》第4章“应用层”的概念,也指出了它和领域层的区别:领域层侧重于表达领域概念,而应用层在领域之上层,增加持久化等概念交易概念、交易概念等软件的典型概念,提供了满足特定场景的外部功能。表示层在应用层功能之上定义了与外部系统通信的具体形式。这也将数据访问层变成了基础设施层。基础设施层为其他层提供支持其概念的具体技术实现。(2)六边形架构2005年六边形架构(译),也称为端口和适配器架构,从设计的角度将代码分为负责业务逻辑的“应用程序”和负责与外部系统交互的“适配器”图案。图片引用自《六边形架构》2013年在IDDD中,Vaughn将六边形架构与DDD相结合,将“应用”细分为“应用”和“领域模型”。图片引用自《实现领域驱动设计》第4章2008年的洋葱架构类似。六边形架构从另一个角度审视一个理想的架构,将领域层放在中心位置,以突出其核心地位。(3)CleanArchitectureBob大叔在2012年提出了cleanarchitecture,一般来说我们认为cleanarchitecture的四层(四个圆圈)基本对应IDDD的六边形架构,但是cleanarchitecture将adapter分为adapter和框架。耦合的“Frameworks&Drivers”层和负责内外层数据转换的“InterfaceAdapters”层。图片引用自《整洁架构》Cleanarchitecture也用“用例”来描述业务实体以外的一层,对应“应用层”,更明确的表明这一层的职责是实现每一个用例。更有趣的是,干净的架构将网关接口放在领域层之外的“用例层”。这使得领域层只关注当前上下文的逻辑,而让用例层负责与其他上下文/资源库的协调和编排。CleanArchitecture还讨论了如何处理框架和架构之间的关系。(4)清晰的架构2017年出现了融合DDD、洋葱架构、整洁架构、CQRS的清晰架构。2.重新思考以上架构足以指导每个具体业务功能的分解。但是在真实的项目中,除了各个具体功能的分层之外,其实还有一些针对平台和框架的配置,这些配置其实和各个业务功能的代码是不一样的,独立于代码结构。此外,每一层都会有一些可重用的代码。比如领域层的基础业务异常,应用层的事务处理,适配器层的HTTP客户端。这些不仅用于单个模块或单个服务,还可以用于多个服务;有的已经有第三方工具,有的需要自己定义封装。我看到很多项目不区分以上两类代码,而是把不属于其他层的代码都放到基础设施层。让糟糕的基础设施层逐渐变成垃圾桶。3.领域模型设计在战术层面划分完结构之后,我们来看一下核心领域模型。在这个类别中,主要讨论基于面向对象技术,如何用领域模型来表达业务概念。为什么使用领域模型模型而不是Service+数据模型模型?复杂的业务逻辑如果采用数据模型的方式,那么Service中就会有很多复杂的逻辑,代码也很难维护。领域模型充分利用面向对象技术的优势,将复杂性转化为职责明确的组件组合,并使每个组件相对简单,以减少认知负荷,提高可维护性。这就是设计的力量。那么为什么要使用面向对象技术呢?面向对象的思想更符合我们认识复杂问题的方式,而现代编程语言普遍支持面向对象,所以DDD选择了面向对象技术。1.关注点分离模式在这个类中,主要使用《领域驱动设计》中的模式。我们从关注点分离的角度分析这些模式。(1)领域对象的生命周期类型从生命周期的角度,“领域对象”分为以下几种类型:与应用程序的生命周期一致,在应用程序启动时创建,在应用程序关闭时销毁。例如《领域驱动设计》5.4.1中的“转账”。在业务过程中创建,会保留一段时间,应用关闭时不会销毁。比如电子商务系统中的“订单”。它在业务过程中创建,使用后销毁。比如对象之间传递的一些参数对象。在《领域驱动设计》的第5章中,Eric也将领域对象分为三种重要的模式:实体、值对象和领域服务。这三种模式和生命周期如何对应?对于类型1,它与应用生命周期一致,即领域服务模型。对于类型2,它是在业务过程中创建的,会保留一段时间,对应实体和值对象。对于类型3,在业务流程中创建,然后销毁,对应值对象。VALUEOBJECT通常用作对象之间传递消息的参数。它们通常是在一次操作中创建然后丢弃的临时对象。——《领域驱动设计》5.3值对象(二)分离领域对象的创建、查询、存储和使用从生命周期的角度,对于这三类领域对象的创建逻辑,可以使用工厂模式进行封装他们在工厂里。对于2类领域对象的保留和后续查询,可以采用Repository模式,将其模拟为一个集合进行访问操作。Eric将Factory和Repository归类为“支持对象”,以将它们与用于表示模型的其他领域对象分开。(3)函数和命令分离使用无副作用的函数模式,将无副作用的查询逻辑提取到一个无副作用的函数中,使有副作用的命令尽可能简单。同样的原因,我也在考虑将带有IO操作的逻辑抽取出来,直接从应用层调用,而不是和其他业务逻辑结合起来。(4)分离域算法采用Strategy模式,将业务逻辑中的变化点放入策略对象中,使不同的实现可以互换,从而实现关注点分离。(5)采用Specification模式将领域内的规则分离,将领域内判断是非的业务规则放入规范对象中。(6)做什么和怎么做分离Intention-RevealingInterface和CohesiveMechanism模式用于将“做什么”和“怎么做”分开。让解释界面专注于表达意图,方便调用者使用;让内聚机制封装实现细节,解决解释接口背后的问题。2.重新思考我发现在OOBootCamp中得到的模型往往不能直接用于实际项目中,这让我从新的角度重新学习和思考领域模型。在实际项目中,设计者往往会过早地陷入对一些特定模式的识别,比如实体、聚合、领域服务,而忽略了如何设计一个能够表达领域概念的模型。我们应该基于领域概念来设计领域模型,然后使用合适的模式来降低领域模型的复杂度,进一步增加领域模型的表达能力。虽然很多项目也采用以领域为中心的架构,但设计者还是以数据模型/贫血模型的方式思考,将大量领域逻辑放在通用Service中,让领域概念隐藏在冗长的流程代码中,确实根本享受不到DDD的好处。软件的核心是它为用户解决领域相关问题的能力。——《领域驱动设计》第一部分,在学习了很多让我们眼花缭乱的方法之后,我们回到了DDD的初衷,重新审视了软件设计与DDD的关系,让DDD帮助我们提升软件设计能力。原文链接:当我们谈论DDD时,我们在谈论什么(qq.com)
