见过太多代码出问题不是业务难,而是业务逻辑全堆在 Service 里的项目。二十万行代码里一个 Service 类七八千行,一个方法能同时干五件事:参数校验、查库、算价格、调外部接口、组装返回结果。改一个促销规则要在三个模块里找代码,稍不留神就漏改一个入口。这种问题不是某个人代码水平差,而是架构上根本没有把业务复杂度拆开。DDD 分层就是专门解决这种问题的思路,这套方法的核心不是教你多写几个包,而是重新划分代码的职责边界,让业务规则有一个稳定的归属地。
这篇文章是 DDD 学习系列的第 1 课,主题是项目介绍和 DDD 分层架构。我会用真实项目的视角,把四层架构的职责边界、依赖规则、落地顺序讲清楚,并附上完整的订单场景示例。适合正在从传统三层架构往 DDD 转型的后端开发,也适合刚接触 DDD、想知道分层到底是什么的读者。看完之后你至少能回答三个问题:为什么要分这几层、每层该写什么不该写什么、怎么在项目里真正落下去。
1. 这个学习项目到底在学什么
1.1 DDD 要解决的真实痛点
领域驱动设计(Domain-Driven Design,简称 DDD)最早是 Eric Evans 在 2004 年提出的,但这两年又火起来,不是因为大家突然爱学习了,而是微服务拆到一定程度后,很多人发现服务内部还是一团乱麻。微服务只是解决了系统之间的边界问题,服务内部的代码质量反而更容易失控。
举个很常见的例子:一个订单模块,传统写法通常是 Controller->Service->DAO 三层。Controller 拿到请求,调 Service 里的 createOrder 方法,Service 里先校验用户、查商品、算优惠、扣库存、生成订单号、再插入数据库。听上去没问题,但业务规则一变就麻烦了。促销规则从“满 100 减 20”改成“满 100 打 8 折且限购 5 件”,你得在 Service 里找到那段算价逻辑,小心翼翼地改掉,还要祈祷没有别的调用方也在用这个字段。如果再来个“部分地区不参与活动”,Service 的判断条件会越来越长,最后变成没人敢动的屎山。
DDD 的思路是:不要在 Service 里堆业务逻辑,而是把业务的核心规则放到领域模型里。所谓“领域模型”,就是把某个业务概念(比如订单、商品、库存)建模成代码对象,对象自身就知道该怎么校验、怎么计算、怎么改变状态。Service 退化成只做流程编排,不负责业务判断。这个过程落到代码结构上,就是 DDD 分层。
1.2 第 1 课的学习边界与整体路线
DDD 本身是个很大的知识体系,光战术设计就有实体、值对象、聚合、仓储、领域服务、领域事件一堆概念,更不用说战略设计里的限界上下文、上下文映射、事件风暴这些。第 1 课不贪多,只专注于两件事:项目介绍和分层架构。项目介绍让你知道这套系列课会用一个贯穿始终的实战项目来演示,不是讲完理论就散;分层架构则把代码的物理结构搭出来,让后续的每个 DDD 概念都有地方安放。
打个比方,分层架构是盖房子的框架,实体、值对象是砖块,聚合是预制板,领域事件是门窗。第 1 课先把框架立起来,后面往框架里塞砖块就顺理成章了。如果一上来就讲聚合和领域事件,没有分层做承载,听的时候全懂,写代码的时候还是不知道放到哪个包、依赖谁、被谁依赖。
学习路线后面的安排大致是这样的:第 2 课讲实体与值对象,第 3 课讲聚合与仓储,第 4 课讲领域服务与应用服务怎么拆分,第 5 课讲领域事件与集成。每一课都会回到第 1 课搭好的分层骨架里,往里面填东西。所以这一课的分层不是一次性讲完就丢掉,而是贯穿整个系列的骨架。
1.3 哪些人适合先看这一课
如果你是写后端三年左右的开发者,日常工作是 CRUD 加简单业务逻辑,觉得现在代码还“能跑”,但总隐隐觉得不对劲,开始关注分层和架构,那这一课非常适合你。如果你已经在团队里尝试过 DDD,但是把领域对象只当成只有 getter/setter 的 POJO,业务逻辑还是写在 Service 里,那这一课刚好帮你纠正方向。如果你是技术负责人,想评估 DDD 能否引入团队,可以先通过分层部分判断落地成本,这一课的实操场景可以直接拿去做内部分享的素材。
如果你是完全没有后端经验的新人,这一课也能看,但建议先补一下 Spring 和数据库的基础,否则代码示例部分会有点吃力。DDD 不会降低编码门槛,它只是改变了组织代码的方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DDD 分层架构的设计思路
2.1 为什么传统三层架构会失控
传统三层架构把系统分成 Controller、Service、DAO 三个层次,看起来清晰明了,但随着业务增长,Service 层会越来越膨胀。原因是三层架构只规定了“调用关系”,没有规定“职责边界”。Service 里既承担了业务流程编排,又承担了业务规则校验,还承担了多表关联查询,甚至偶尔还要组装数据返回给前端。所有不属于它该干的事,因为没有明说它不该干,最后都压到它头上。
真正的失控点是“业务规则散落”。同一个需求,比如“订单总额超过 1000 元需要走人工审核”,可能在 Controller 里有一份判断、Service 里有一份判断、某个定时任务里又写了一份判断。当规则改成 2000 元的时候,你根本不知道要改几处。三层架构没有提供一个“唯一放业务规则”的地方,所以规则就被到处复制。
DDD 分层的第一步,就是强制规定:业务规则只放在领域层。其他层可以调用领域层,但不得自己实现核心业务规则。这个约束比“分层”本身更重要。
2.2 分层的本质是边界与依赖规则
DDD 经典四层架构从上到下分别是:用户接口层(Interface)、应用层(Application)、领域层(Domain)、基础设施层(Infrastructure)。这里要特别注意,网上很多图把基础设施层画在最上方,也有画在最下方的,方向不是重点,重点在于依赖关系是自上而下还是自下而上。
严格按 DDD 的依赖规则,用户接口层依赖应用层,应用层依赖领域层,基础设施层也依赖领域层,但领域层不依赖任何其他层。换句话说,领域层是独立于技术细节的纯业务模块,这也是 DDD 最核心的一条依赖规则。现实中常用 Spring 框架,领域层往往不引入 Spring 的注解,也不直接依赖 MyBatis/JPA 的注解,就是为了保持纯净。
这条规则的直接好处是:业务规则可以脱离数据库和框架独立测试。你想验证“订单金额满 1000 需要人工审核”,只需要 new 一个 Order 对象,调用领域方法,断言返回值,不需要启动 Spring 容器,也不需要连数据库。测试速度从秒级降到毫秒级,开发反馈循环大大缩短。
2.3 依赖倒置:让领域层成为系统的中心
如果领域层不依赖基础设施层,那数据库操作怎么办?答案是用接口隔离。在领域层定义一个仓储接口(Repository),比如 OrderRepository,里面声明 save(Order order) 和 findById(OrderId id) 方法。基础设施层提供该接口的实现类,真正去操作数据库、调用 MyBatis 的 Mapper。领域层只依赖接口,不关心实现。
这就是依赖倒置原则(DIP,Dependency Inversion Principle)在 DDD 里的具体应用。依赖倒置讲的是:抽象不应该依赖细节,细节应该依赖抽象。放在 DDD 分层里,仓储接口是抽象,数据库操作是细节,接口定义在领域层,实现放在基础设施层,所以基础设施层依赖领域层。
刚接触这套思想的人容易抵抗:“这么绕一圈,数据操作不还是要写吗?”没错,代码量并不会减少,但依赖方向变了。以前是业务代码依赖数据库技术,现在是数据库技术依赖业务接口,这个“颠倒”带来的好处是业务层完全不用关心数据库选型,你可以用 MySQL、PostgreSQL、内存数据库或者一个假的 Mock 实现来测试业务,业务代码一行都不用改。
