最近准备重新整理一下 DDD 相关的内容。DDD 这个东西说实话我之前也断断续续看过不少资料但是很多文章一上来就是领域、实体、值对象、聚合根、限界上下文、领域事件各种名词直接往脸上砸。看完以后好像都懂了真到项目里面写代码又不知道从哪里开始。所以这个系列我不打算一开始就堆概念而是尽量从我们平时写 Java 后端项目的习惯出发先看看为什么会需要 DDD再慢慢过渡到它到底是什么、怎么落地。这一篇作为第一篇先解决两个问题DDD 到底是什么为什么我们需要学习 DDD。话不多说开始今天的学习。一、先从平时写代码说起相信大多数 Java 后端项目最常见的结构都是这种controller service mapper entity vo dto或者再细一点controller service service.impl mapper entity dto vo query这种结构本身没有问题。刚开始做项目的时候这种分层非常清晰Controller 负责接收请求Service 负责处理业务逻辑Mapper 负责操作数据库Entity 对应数据库表DTO、VO 负责接口入参和出参。对于简单系统来说这样写非常舒服开发效率也很高。比如一个用户管理模块无非就是新增用户、修改用户、删除用户、查询用户。按照这种结构写基本没有什么理解成本。但是项目只要持续迭代一段时间问题就慢慢出来了。二、MVC 项目常见的问题我们平时开发业务很多时候是这样一个流程产品提需求后端先看需要哪些表设计表字段写 Entity、Mapper在 Service 里面拼业务逻辑Controller 暴露接口。这个流程看起来很自然因为大家基本都是这么写的。但是这里面有一个隐藏问题我们很容易变成“数据库驱动开发”。也就是说我们不是先从业务角度思考这个系统里面有哪些核心对象、对象之间是什么关系、每个对象应该承担什么职责而是先从表结构开始想。表一旦定下来后面的代码基本就围着表转。久而久之Service 就会越来越胖。比如一个订单 Service刚开始可能只有下单逻辑publicvoidcreateOrder(CreateOrderRequestrequest){// 校验商品// 校验库存// 计算金额// 保存订单// 扣减库存}后面需求来了优惠券、积分、满减、会员折扣、预售、取消订单、退款、部分发货、售后。如果没有一个清晰的业务边界代码很容易就变成这样publicvoidcreateOrder(CreateOrderRequestrequest){// 校验商品// 校验库存if(request.getCouponId()!null){// 处理优惠券}if(request.getUsePoint()){// 处理积分}if(request.getMemberLevel()!null){// 处理会员折扣}if(request.getPreSale()){// 处理预售逻辑}// 计算金额// 保存订单// 扣减库存// 发送消息}当然真实代码肯定不会只有这么简单。实际项目里面还会夹杂各种数据转换、RPC 调用、状态判断、事务处理、异常兜底、消息发送。最后一个 Service 方法可能几百行一个 Service 类可能几千行。这种代码不是不能跑它能跑而且可能已经在线上跑了好几年。问题是后面不好改。常见现象就是新需求来了只敢复制一份旧逻辑改一改一个状态字段被十几个地方判断改一个小逻辑需要看半天上下游影响业务规则散落在 Controller、Service、Mapper、定时任务里面同一个业务含义在不同方法里面有不同写法老代码不敢动新逻辑只能继续往上堆。这时候我们会发现真正麻烦的不是技术框架而是业务逻辑没有被管理好。三、DDD 是什么DDD全称是 Domain Driven Design翻译过来就是领域驱动设计。简单来说DDD 是一种以业务领域为核心来设计系统的方法。这里面有两个关键词领域驱动。“领域”可以理解为业务范围。比如电商系统里面订单、支付、库存、商品、会员、营销都可以看成不同的业务领域。“驱动”可以理解为系统设计应该被业务驱动而不是单纯被数据库表、接口字段、页面展示驱动。也就是说DDD 更关注这些问题这个业务里面的核心对象是什么核心对象应该有哪些行为哪些规则必须放在对象内部保证哪些业务属于同一个边界哪些逻辑不应该互相影响外部系统变化时怎么减少对内部业务的影响。通俗一点讲DDD 不是教我们怎么写一个 Controller也不是教我们怎么写一个 Mapper。它更像是在提醒我们不要只盯着数据库表和 CRUD要先把业务模型想清楚。四、DDD 和普通分层有什么区别普通 MVC 分层里面最常见的业务承载位置是 Service。Entity 很多时候只是数据库表的映射对象里面只有字段没有行为。比如订单对象可能长这样publicclassOrder{privateLongid;privateStringorderNo;privateIntegerstatus;privateBigDecimaltotalAmount;}它只是一个数据容器。订单能不能取消、能不能支付、能不能发货这些规则往往都写在 Service 里面。而 DDD 更希望业务规则尽量靠近业务对象本身。比如订单取消这件事不应该只是 Service 里面随便改一个状态而应该体现出订单自己的业务规则publicclassOrder{privateLongid;privateStringorderNo;privateOrderStatusstatus;publicvoidcancel(){if(!OrderStatus.WAIT_PAY.equals(this.status)){thrownewIllegalStateException(只有待支付订单才能取消);}this.statusOrderStatus.CANCELED;}}这里只是一个很简单的例子。重点不是这段代码写得多高级而是业务规则的位置发生了变化。以前是 Service 在外面判断订单能不能取消现在是 Order 自己知道自己能不能取消。这样做的好处是订单相关的规则更容易收拢到订单模型里面而不是散落在系统各处。当然实际项目不会这么简单。订单取消可能还涉及库存释放、优惠券返还、支付关闭、消息通知。这些就需要应用服务、领域服务、领域事件等东西配合起来。后面的文章会慢慢展开。五、为什么要学习 DDD我觉得学习 DDD最主要不是为了显得架构高级也不是为了简历上多写一个关键词。更现实的原因是当业务复杂到一定程度之后我们需要一种方法把复杂度管起来。1. 帮助我们重新理解业务边界很多系统最开始并不复杂。但是随着需求不断增加模块之间的边界会越来越模糊。比如订单模块里面写了库存逻辑库存模块里面又判断订单状态营销模块里面还要关心支付结果。短期看功能上线了。长期看模块之间互相知道得太多改起来就非常难受。DDD 里面的领域、子域、限界上下文就是用来帮助我们重新拆业务边界的。边界清楚之后系统里面哪些逻辑应该放在一起哪些逻辑应该隔离开就会更明确。2. 帮助我们减少 Service 大杂烩很多项目最后都会变成 Service 层越来越重。不是 Service 这个名字有问题而是所有业务逻辑都堆在 Service 里面最后谁也看不清楚。DDD 会把一部分业务规则放回领域模型里面。应用服务负责流程编排领域对象负责自身规则仓储负责持久化防腐层负责隔离外部系统。这样每一层的职责会更清楚。3. 帮助我们写出更贴近业务的代码普通代码里面我们经常看到这种方法名updateStatus saveOrder queryList handleData process这些方法不是不能用但业务含义不够明确。如果换成更贴近业务的表达可能会变成createOrder cancelOrder payOrder confirmReceipt applyRefund再进一步订单对象内部可能会出现order.cancel();order.pay();order.confirmReceipt();这时候代码读起来就更像业务语言。业务语言越清楚后面维护的人越容易理解。4. 帮助我们应对复杂业务变化DDD 比较适合业务规则多、状态流转复杂、多人长期维护的系统。比如订单系统支付系统会员系统营销系统审批流系统计费系统中台类系统。这些系统的特点是不是简单 CRUD业务规则会不断变化。如果每次变化都靠 if/else 往上补代码迟早会变得很难维护。DDD 的价值就是尽量让业务模型稳定下来让变化发生在相对合适的位置。六、DDD 不是什么这里也要泼一点冷水。DDD 不是银弹。它不是用了之后代码就一定优雅也不是所有项目都应该上 DDD。如果一个系统只是简单的后台管理主要就是单表 CRUD生命周期也不长那直接 MVC 就很好。为了 DDD 而 DDD最后很可能会出现这种情况包结构变复杂了类变多了原来一个 Service 能写完现在绕了好几层团队成员看不懂业务没复杂起来代码先复杂起来了。这就有点得不偿失。所以我理解的 DDD不应该是一套必须照抄的模板而是一种拆解复杂业务的方法。能完整落地当然好。如果项目暂时不适合完整落地也可以先借鉴它里面的一些思想比如先梳理业务边界把核心业务规则收拢到模型里外部系统调用增加防腐层少写没有业务含义的大方法用业务语言命名类和方法。这些改动不一定非要等项目重构时才做平时写新需求的时候也可以慢慢调整。七、什么时候适合用 DDD简单整理一下是否适合考虑 DDD 的场景有这些场景是否适合单表 CRUD 后台不太适合临时活动页面不太适合业务规则简单的小系统不太适合订单、支付、营销这类复杂业务比较适合长期迭代的产品系统比较适合多团队协作维护的系统比较适合业务边界已经开始混乱的老系统可以逐步引入所以不是所有项目都要用 DDD。我们学习它更多是为了在遇到复杂业务的时候手里多一套分析和落地的方法。八、总结这一篇我们先简单聊了一下 DDD 是什么以及为什么要学习 DDD。简单总结一下DDD 是领域驱动设计核心是从业务领域出发设计系统普通 MVC 项目容易把业务逻辑堆到 Service 里面DDD 希望通过领域模型收拢业务规则让代码更贴近业务DDD 更适合复杂业务、长期迭代、多团队维护的系统DDD 不是银弹简单 CRUD 项目没必要硬套。到这里我们只是对 DDD 有了一个大概印象。下一篇文章我们继续从 MVC 三层架构说起看看我们熟悉的开发模式到底有哪些优点和问题。只有先看清楚原来的问题后面再学习 DDD 的时候才不会觉得它是在硬凑概念。