一般来说,领域建模是属于战略层的,而DDD工程落地是属于战术层的,两者是否结合使用,视实际情况而定,比如传统的MVC架构也能使用DDD进行领域建模,DDD架构最好是先做DDD领域建模。
在进行具体的实践之前我们必须对一些专有名词进行了解,本章,就带着大家一起去对领域驱动设计有个初步的认知。
作为一个开发者,我们肯定接手过其他的人的项目。我想你一定有个这样的经历:
面对冗杂的系统,模块彼此关联,没有人能描述清楚每个细节,没有文档,即使有文档也和系统对不上。当新需求需要修改一个功能时,往往光回顾该功能涉及的流程就需要很长时间,更别提修改带来的不可预知的影响面。于是 RD 就加开关,小心翼翼地切流量上线,一有问题赶紧关闭开关。
面对此般场景,你要么跑路,要么重构。
重构是克服演进式设计中大杂烩问题的主力,通过在单独的类及方法级别上做一系列小步重构来完成,我们可以很容易重构出一个独立的类来放某些通用的逻辑,但是,你会发现你很难给它一个业务上的含义,只能给予一个技术维度描绘的含义。你正在一边重构一边给后人挖坑。
对于一个架构师来说,在软件开发中如何降低系统复杂度是一个永恒的挑战,虽然通过一系列的设计模式或范例来降低一些常见的复杂度。但是问题在于,这些理念是通过技术手段解决技术问题,但并没有从根本上解决业务的问题。
如果你也有这方面的苦恼,那么ddd 的思想也许能为你带来启发。
DDD,全称Domain-Driven Design, 中文叫领域驱动设计,是一套应对复杂软件系统分析和设计的面向对象建模方法论。
其本质是一种处理复杂领域的设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。
它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。
DDD是面向对象的设计思想,是面向对象设计的一种升华。
领域驱动设计提出了一套核心构造块(Building Blocks,如聚合、实体、值对象、领域服务、领域工厂、仓储、领域事件,等),这些构造块是对面向对象领域建模的一些核心最佳实践的浓缩。
这些构造块可以使得我们的设计更加标准、有序。
可能很多人看到这个标题又开始疑惑,什么又是数据驱动?提到数据驱动可能很多人陌生,但是提到MVC架构就很熟悉了,传统的数据驱动开发模式,View、Service、dao这种三层分层模式,开发者会很自然的写出过程式代码,这种开发方式中的对象只是数据载体,而没有行为,是一种贫血对象模型。
以数据为中心,以数据库ER图为设计驱动,分层架构在这种开发模式下可以认为是数据处理和实现的过程。
读到此处你又了解到一个名词:贫血对象模型,与之相对应的还有一个叫做充血对象模型,由于这两个名词会以很大的频率出现在我们后续的学习过程中,所以此处先给大家简单描述一下两个名词的含义以及之间的联系
在领域驱动设计(Domain-Driven Design, DDD)中,贫血模型和充血模型是用来描述对象职责分配的不同方式。这两种模型的主要区别在于领域对象是否包含业务逻辑。
1.贫血模型:
优点:结构清晰,领域对象简单且易于理解;由于业务逻辑与数据分离,可能更容易进行单元测试和替换持久化策略。
缺点:业务逻辑与数据分离可能导致上下文切换增加,业务规则不易于管理和维护;领域模型显得功能不足,失去了面向对象的核心特性——封装。
2.充血模型:
Setter优点:遵循面向对象原则,业务逻辑与数据紧密结合,有利于形成具有丰富表达力的领域模型;更易于保持业务规则的一致性和完整性。
Setter缺点:如果设计不当,可能会导致领域对象变得庞大而难以理解和维护;同时,为了确保领域模型的纯净和可重用性,需要精心设计接口以避免过度耦合。
总结来说,在DDD实践中,Setter充血模型更符合其核心理念,即通过Setter捕获领域内的本质概念和逻辑来构建强内聚、松耦合的领域模型,从而更好地反映真实世界的业务问题。然而,选择哪种模型并不是绝对的,实际项目中往往结合两种模型的特点进行折衷考虑,关键是要根据具体业务场景和团队习惯来决定如何组织和封装业务逻辑。
以前的系统分析和设计是分开的,导致需求和成品非常容易出现偏差,两者相对独立,还会导致沟通困难,DDD 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。
DDD 的宏观理念其实并不难懂,但是如同 REST 一样,DDD 也只是一个设计思想,缺少一套完整的规范,导致DDD新手落地困难。
由于 DDD 不是一套框架,而是一种架构思想,所以在代码层面缺乏了足够的约束,导致 DDD 在实际应用中上手门槛很高,甚至可以说绝大部分人都对 DDD 的理解有所偏差。
举个例子(贫血域模型)在实际应用当中层出不穷,而一些仍然火热的 ORM 工具比如 Hibernate,Entity Framework 实际上助长了贫血模型的扩散。
同样的,传统的基于数据库技术以及 MVC 的四层应用架构(UI、Business、Data Access、Database),在一定程度上和 DDD 的一些概念混淆,导致绝大部分人在实际应用当中仅仅用到了 DDD 的建模的思想,而其对于整个架构体系的思想无法落地。
从单机的时代,服务化的架构还局限于单机 +LB 用 MVC 提供 Rest 接口供外部调用,到今天,在一个所有的东西都能被称之为“服务”的时代(XAAS),人们在踩过诸多拆分服务的坑(拆分过细导致服务爆炸、拆分不合理导致频分重构等)之后,开始死锁原因了。
DDD 的思想让我们能冷静下来,去思考到底哪些东西可以被服务化拆分,哪些逻辑需要聚合,才能带来最小的维护成本,而不是简单的去追求开发效率。
假如我们现在要做一个电商订单下单的需求,涉及到用户选定商品,下订单、支付订单、对用户下单时的订单发货:
综上,DDD 整体作用总结如下:
有了 DDD 的指导,加之微服务的事件,才是完美的架构。
系统的复杂度越来越来高是必然趋势,原因可能来自自身业务的演进,也有可能是技术的创新,然而一个人和团队对复杂性的认知是有极限的,就像一个服务器的性能极限一样,解决的办法只有分而治之,将大问题拆解为小问题,最终突破这种极限。
微服务在这方面都给出来了理论指导和最佳实践,诸如注册中心、熔断、限流等解决方案,但微服务并没有对“应对复杂业务场景”这个问题给出合理的解决方案,这是因为微服务的侧重点是治理,而不是分。
我们都知道,架构一个系统的时候,应该从以下几方面考虑:
微服务在第二个做得很好,但第一个维度和第三个维度做的不够。这就给 DDD 了一个“可乘之机”,DDD 给出了微服务在功能划分上没有给出的很好指导这个缺陷。所以说它们在面对复杂问题和构建系统时是一种互补的关系。
知道了 DDD 与微服务还不够,我们还需要知道他们是怎么协作的。
一个系统(或者一个公司)的业务范围和在这个范围里进行的活动,被称之为领域,领域是现实生活中面对的问题域,和软件系统无关,领域可以划分为子域,比如电商领域可以划分为:商品子域、订单子域、发票子域、库存子域 等
在不同子域里,不同概念会有不同的含义,所以我们在建模的时候必须要有一个明确的边界,这个边界在 DDD 中被称之为限界上下文,它是系统架构内部的一个边界,《整洁之道》这本书里提到:
系统架构是由系统内部的架构边界,以及边界之间的依赖关系所定义的,与系统中组件之间的调用方式无关。
所谓的服务本身只是一种比函数调用方式成本稍高的,分割应用程序行为的一种形式,与系统架构无关。
所以复杂系统划分的第一要素就是划分系统内部架构边界,也就是划分上下文,以及明确之间的关系,这对应之前说的第一维度(功能维度),这就是 DDD 的用武之处。
其次,我们才考虑基于非功能的维度如何划分,这才是微服务发挥优势的地方。
假如我们把服务划分成 ABC 三个上下文:
我们可以在一个进程内部署单体应用,也可以通过远程调用来完成功能调用,这就是目前的微服务方式,更多的时候我们是两种方式的混合,比如 A 和 B 在一个部署单元内,C 单独部署,这是因为 C 非常重要,或并发量比较大,或需求变更比较频繁,这时候 C 独立部署有几个好处:
架构是可以演进的,所以拆分需要考虑架构的阶段,早期更注重业务逻辑边界,后期需要考虑更多方面,比如数据量、复杂性等,但即使有这个方针,也常会见仁见智,没有人能一下子将边界定义正确,其实这里根本就没有明确的对错。
即使边界定义的不太合适,通过聚合根可以保障我们能够演进出更合适的上下文,在上下文内部通过实体和值对象来对领域概念进行建模,一组实体和值对象归属于一个聚合根。
有了聚合根,再基于一些相应的约束,未来可以根据需要,把聚合根升级为上下文,甚至拆分成微服务,都是比较容易的。
按照 DDD 的约束要求:
其实DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。
而微服务追求业务层面的复用,设计出来的系统架构和业务一致;在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。
可以参见下图来更好地理解双方之间的协作关系:
我们简单的对上面的内容做一个书面的总结,领域驱动设计(DDD)与微服务架构在协作时,能够相互支持并形成一个强大且灵活的系统构建框架。
DDD提倡根据业务领域的复杂性和重要性将大型系统划分为多个有界上下文(Bounded Contexts),每个有界上下文对应着一个特定的业务子域。
微服务架构主张单个服务应该具备单一职责原则,并专注于解决一个独立的业务问题。
在实践中,DDD中的子域可以映射到微服务中,每个微服务负责实现和维护一个或相关的几个业务子域的逻辑,从而清晰地定义了服务间的边界。
DDD强调领域模型必须是内聚的、反映真实业务规则的实体和值对象集合。
每个微服务内部都包含其专属的领域模型,这些模型可以独立演化而不影响其他服务,体现了微服务的自治特性。
领域模型的设计指导微服务内的业务逻辑封装,并确保数据一致性及业务规则的完整执行。
DDD提倡与领域专家合作,创建一种通用语言来表达业务概念和规则。
在微服务环境中,每个团队专注于自己的服务,使用基于DDD建立的通用语言,这有助于团队间沟通减少误解,同时也能保证每个服务内部的业务处理准确无误。
DDD通过模块化设计使复杂业务可拆解为小的、易于管理的部分,而这些部分在微服务架构下成为单独的服务单元。
微服务架构下的每个服务可以独立开发、测试、部署和扩展,使得每个服务团队能够遵循DDD原则快速迭代领域模型和业务逻辑,并以敏捷的方式响应业务变化。
在DDD中,不同有界上下文之间通过定义明确的接口(例如:发布/订阅事件、API调用等)进行交互。
微服务之间的通信也需遵循类似的机制,利用API Gateway、消息队列、异步事件驱动等方式降低耦合度,保持服务之间的松耦合关系。
DDD鼓励每个有界上下文拥有自己的数据库,以便更好地符合其领域模型的需求。
微服务架构同样建议每个服务拥有自己的数据库或者数据存储,确保数据的局部一致性和服务的独立性。
总之,在DDD指导下设计和实施微服务架构,可以让每个微服务都有坚实的业务基础,并能够在技术层面上有效地支撑和服务于业务需求,从而形成一套既能应对复杂业务场景又能保障系统灵活性和可扩展性的架构方案。