魔神公寓
94.55M · 2026-04-07
传统的三层架构(表现层、业务逻辑层、数据访问层)或MVC模式,在系统建设初期往往简单高效。但随着业务复杂度的提升,系统容易陷入“代码泥潭”:业务逻辑分散在各层,数据与行为分离(贫血模型),导致系统难以维护、扩展,最终出现“系统老化”现象。
领域驱动设计(DDD)并非完全抛弃分层,而是对传统架构的升级与重构。它将关注点从“技术实现”转移到“业务领域”,通过建立反映业务本质的领域模型,确保代码结构随着业务的发展而持续演进,从而有效遏制系统老化,保持系统的生命力。
DDD与MVC/三层架构的核心分歧在于驱动力的不同:
MVC/三层架构(技术优先) : 关注点:侧重于代码的组织形式和技术分层(如Controller、Service、DAO)。 痛点:业务逻辑往往被淹没在技术细节中。业务人员看到代码中的“类”和“表”时一头雾水,无法理解系统是如何运作的。这导致业务人员完全无法参与技术讨论,只能被动等待功能交付。
DDD(业务优先) : 关注点:侧重于业务逻辑、领域规则和业务流程。代码结构直接映射业务概念(如“订单”、“支付”、“库存”)。 优势:降低沟通壁垒,促进业务参与。 统一语言:开发人员使用的类名、方法名与业务人员口中的术语完全一致。 可视化业务:即使是不懂代码的业务人员,看到“订单聚合”、“支付领域服务”这样的结构,也能大致理解系统的核心脉络。 共同设计:业务人员不再是需求的“甩手掌柜”,而是能参与到领域模型的构建中,确保软件真正解决业务问题。
是的,确实很抽象,充满了诸如“聚合根”、“限界上下文”、“防腐层”等抽象术语。这些词很抽象,但目的不是为了创造新词汇,目的是为了解决“怎么让写出来的代码,真正符合复杂的业务逻辑,而不是变成一团乱麻。 ”这一问题。
这些抽象概念主要分为两种:战略设计和战术设计。
战略设计在宏观角度来看就是给项目分地盘,是用来划分边界的。主要包含四个抽象概念:
领域与子域:领域是软件系统所要解决的全部业务问题和规则的集合,它定义了我们业务的边界和范围。例如,电商系统的领域涵盖了从商品展示、用户下单、支付处理到物流配送的全过程。面对一个庞大而复杂的领域,直接进行系统设计会异常困难。因此,DDD采用“分而治之”的策略,将一个大的领域分解为多个更小、更易管理的子域。 子域的划分并非随意进行,而是根据其对业务的核心价值和战略重要性,将其分为三类:
统一语言为技术核心:就是说可以让技术人员和业务人员包括测试人员都可以用相同的“频率”来交流,不至于出现说是客户的目的是做一个秋千,而技术人员和业务人员对这个秋千的理解不一样,从而设计的秋千也是千奇百怪,导致难以统一一起,难以满足客户需求。以统一语言为技术核心,强调的是要在每一个项目内形成一个通用的语言。 这不仅仅是包结构类似,更重要的是命名的一致性。统一语言必须体现在代码的命名(类名、方法名、变量名)、数据库表名以及测试用例名中。如果业务方说“下订单”,代码里就必须是 placeOrder 而不是 createOrder 或 saveOrder。代码即文档,看到代码中的类名和方法名,就应该能直接读懂业务逻辑。
限界上下文:意思是同一个词,在不同的业务场景里,含义是不一样的,这里的不同业务场景就是上下文。我们在实现这一概念的时候,需要把一个系统切分为不同的多个上下文,例如在销售上下文中,商品这一词汇我们关心的是价格和消息,而在物流上下文中,商品的关注点就变为了重量体积等。 关键点:在代码层面,这通常意味着两个完全不同的类。销售上下文有一个 SalesProduct 类(含价格字段),物流上下文有一个 ShippingProduct 类(含重量字段)。限界上下文强调的是模型和逻辑上的彻底隔离,千万不要试图用一个巨大的类包含所有字段。
上下文映射:这个的意思就是上下文的交流,刚刚进行了上下文限界,分割了多个地盘,但是分好后地盘之间需要交流。比如说订单系统的客户需要查询库存,而库存系统的供应商提供查询接口。两个系统的交互就是上下文映射。
战术设计则是在微观的角度进行堆积拼接,而DDD提供了很多拼接的原材料:
实体:有唯一身份证id的对象。不会因为跨越上下文跨越领域而改变其本质(id)。
值对象:没有唯一身份证的,只描述属性且不变的对象。就比如说刚刚提到的实体的其他属性,比如一个人身上的穿的衣服这一属性,一个属性就是一个值对象。
聚合与聚合根:聚合就是把一群强关联性的实体和值对象捆绑在一起形成一个集合(事务边界),聚合根就是这个集合的特殊实体,访问聚合的唯一入口,就是集合的老大。比如说购物的订单就是聚合根,其中的很多订单项就是实体,订单项的金额就是值对象。
领域服务:总有一些业务逻辑不属于任何一个实体,比如说转账涉及双方,无法将这个行为写入任何一个单一的一方,而领域服务就会进行承载。
领域事件:这是解耦的神器,也是连接不同限界上下文的重要纽带。 当一个业务动作完成时(如“订单已支付”),系统会发布一个事件。其他上下文(如积分系统、库存系统)可以订阅这个事件并做出反应(加积分、扣库存),而无需直接调用对方的接口。这实现了系统间的异步解耦。
资源库(又叫仓库):这个就是主要负责和数据库对接,把聚合放入或取出数据库,对其他业务而言,他就是数据库的代理。(-----PS : 资源库 Repository 是聚合的集合。它屏蔽了数据库的存在,让领域层感觉像是在操作内存中的对象集合(List),而不是直接操作数据库表。)
----- 注意以下的区分:
领域服务:负责处理那些不属于单一实体的复杂业务规则(如转账时的汇率计算、跨聚合的校验)。
应用服务:负责协调和编排(如开启事务、调用领域服务、保存数据)。 不要混淆两者,领域服务专注于“ 业务逻辑 ”,应用服务专注于“ 流程控制 ”。
与以往的(MVC)实体不同,DDD中的实体类中是可以写入方法的。在以往的实体类有属性和get/set的基础上写入部分业务逻辑,这种实体被称之为充血模型,与其对应的就是以往的没有业务逻辑的 贫血模型(POJO) 。
还是和以前一样抽象出一个接口,然后再用实现类来实现对应的与数据库交互的具体过程。这样的好处大大的有:
这里要强调一下值对象这个概念。前面提到值对象是一个只描述属性且不变的对象,这个意思是说值对象就是一个类,一个属性类。对应实体类,实体类有id,还有属性,其引用的属性可以就是一个值对象。
这个只是一个设计模式,主要是隔离外部系统(如遗留系统、第三方服务或其他微服务)的模型和接口,防止其“污染”或“侵蚀”你当前系统的领域模型。它不管其他第三方接口怎么实现业务的,它只负责调用接口,然后接收最后的结果。就算未来换了第三方接口,那也无所谓,它只负责要处理这个业务的结果。
针对一些业务场景比如:商家商品标售10元,客户付出12元,商家到手7元。这种涉及多个实体且不适合放在某一个单独实体中的业务时,采用领域服务。
解决的是“类外部太乱”的问题,也就是“分家”的问题。在传统开发中,我们常常会遇到一个数据库表对应一个庞大实体类的情况,这个类包含了所有业务场景可能用到的属性和行为,导致耦合度极高。而DDD通过限界上下文,为不同的业务场景划定清晰的边界。
解决的是“类内部太乱”的问题,也就是“管家”的问题。当我们通过限界上下文确定了一个具体的业务场景(比如银行核心系统中的账户)后,这个Account类内部可能依然会很复杂,包含余额、地址、交易记录等多个紧密关联的部分。
定义:聚合就是将这个Account(作为聚合根)及其紧密相关的值对象(如Money、Address)和实体(如Transaction)封装成一个不可分割的整体。
交互规则:聚合规定了所有对外部世界的交互都必须通过聚合根(Account)来进行,从而确保了聚合内部数据的一致性和业务规则的完整性。例如,修改账户地址必须通过Account的changeAddress方法,而不是直接操作Address对象,这样可以保证在修改地址的同时,可以触发相关的业务逻辑。
事务边界(重要补充) :聚合是数据修改的事务边界。 在一次业务操作中,为了保证数据强一致性,我们只能修改一个聚合内的对象。如果要修改另一个聚合,通常需要通过“领域事件”来异步通知。
判断标准:那怎么确定值对象和实体之间是否需要聚合呢?就看失去这个值对象的时候,值对象在业务中是否就没有作用了。
DDD的四层架构是其战略设计的核心体现,旨在通过清晰的分层实现业务逻辑与技术细节的彻底解耦,构建一个高内聚、低耦合、易于维护和演进的软件系统。这四层分别是:用户接口层、应用层、领域层和基础设施层。
用户接口层是系统与外部世界交互的门户,其职责纯粹而明确:接收外部指令,并返回处理结果。
核心职责
关键原则
应用层是整个业务流程的“编排者”或“指挥家”。它非常薄,不包含任何核心业务规则,其作用是协调领域层中的各个对象,以完成一个完整的业务用例。
核心职责
@Transactional注解),以确保一个完整业务流程的原子性。关键原则
领域层是整个DDD架构的“心脏”和灵魂,它承载着企业最核心的业务知识和业务规则。这一层是纯粹的业务逻辑表达,不依赖任何外部技术细节。
核心职责
实现核心业务逻辑:所有与业务相关的状态变更、规则计算、行为定义都集中于此。
封装业务概念
:包含了之前讨论的所有核心领域构建块:
关键原则
基础设施层为上层提供技术支撑,负责处理所有与技术细节相关的操作,如数据持久化、消息发送、调用外部服务等。
核心职责
关键原则:依赖倒置
理解四层架构,必须清晰地把握数据是如何在各层之间流动的,以及它们之间的依赖方向。
依赖方向
典型请求处理流程
CreateOrderDTO对象。OrderAppService接收到CreateOrderDTO。OrderRepository接口(定义在领域层)加载相关的聚合根(如Customer)。Customer聚合根的placeOrder()方法,传入订单信息。Customer实体内部执行业务逻辑(检查信用、创建Order实体等),状态发生变更。OrderRepository接口,将变更后的Customer和新的Order聚合根保存。OrderRepositoryImpl接收到保存请求,执行具体的SQL操作,将数据写入MySQL数据库。DDD确实存在一个显著的弊端,即类爆炸。
好的项目通常都遵循单体优先的原则。