本节摘要:分层不是 DDD 的发明,但 DDD 把分层的意图改了:传统分层是给"请求"修的路,DDD 分层是给"知识"盖的楼。本节沿时间线走完两层、三层、四层的演进,用青柚商城同一笔下单逻辑在三种形态下的代码对照,说明第四层——领域层——为什么必须单独存在,以及"假四层"这个最常见的烂尾形态长什么样。
第 4 章从一段历史讲起,因为分层的每一步演进都对应一类真实疼过的问题。最早期的业务系统是两层形态:界面层直连数据库——网页脚本里写着查询语句,查询结果直接拼进页面。青柚商城 1.0 的部分页面就是这个年代的产品:一个下单页面文件里同时住着按钮样式、金额计算和三条更新语句。两层的问题很快显形:逻辑没有住址。满减规则写在页面 A,同样的规则又抄在页面 B,改一处漏一处——这段历史在 1.4 的实验里已经重现过,它的架构根源就是两层。
三层架构(表现、业务逻辑、数据访问)给逻辑安了家:业务逻辑层收拢了散落的规则,页面只管展示,SQL 收进数据访问层。这是至今仍在服役的主流形态,青柚商城 2.0 也是三层。它解决了"逻辑没有住址",却没解决"逻辑没有主权"——业务逻辑层实际是个大杂院,流程编排(先存单再扣款再发消息)、业务规则(满减互斥)、数据拼装(把三张表拼成一个返回对象)混居在一起。1.4 的诊断在这里从代码风格上升为架构判断:事务脚本不是某个工程师的坏习惯,是三层架构对逻辑层默认的组织方式。
DDD 的四层架构在三层中间动了一刀,把"业务逻辑层"劈成两间:应用层管流程编排——用例的步骤、事务的边界、对其他上下文的调用;领域层管业务规则——聚合、不变量、领域事件,也就是第 3 章的全部产出。劈开的标准还是那条判据:操心事属于"这个用例的步骤"还是"这个业务永远如此"。
同一笔"下单"在两种形态下的代码对照:
// 三层形态:Service 什么都要管 public class OrderService { public String placeOrder(CartSnapshot cart, CouponView coupon) { if (cart.isEmpty()) throw new BizException("购物车为空"); // 业务规则:券核验 if (coupon != null && !coupon.usableNow(cart.subtotal())) { throw new BizException("券不可用"); } // 业务规则:金额计算 BigDecimal payable = cart.subtotal().subtract( coupon == null ? BigDecimal.ZERO : coupon.amount()); // 数据拼装:手工搬字段 OrderEntity entity = new OrderEntity(); entity.setAmount(payable); entity.setStatus("WAIT_PAYMENT"); orderDao.insert(entity); // 远程调用:扣库存 inventoryClient.deduct(cart.toDeductRequest()); return entity.getId(); } }
// 四层形态:应用层只编排,规则在领域层 public class PlaceOrderUseCase { // 应用层:步骤清单 private final OrderFactory factory; private final OrderRepository orders; private final EventPublisher events; public TradeOrderId execute(CartSnapshot cart, CouponView coupon) { TradeOrder order = factory.createFrom(cart, coupon); // 规则在工厂与聚合里 orders.save(order); events.publish(OrderPlaced.from(order, cart)); return order.id(); } }
四层形态的 execute 只剩三步,业务规则一条都看不见——它们都在 3.2 的工厂与聚合里。对比两段代码能看出四层的真正意图:应用层是"这一单怎么走"的剧本,领域层是"这个生意什么永远成立"的法典。 剧本每月改版(今天加风控步骤、明天换消息队列),法典一年才修订几次;合在一起时,改剧本常常误伤法典。四层不是层数的胜利,是把两种变化频率的知识分开保管。

多团队学完四层,交出来的代码是"假四层":目录里有 domain 文件夹,打开一看——全是字段加 getter setter 的数据类,规则照旧住在应用层的大方法里。层是有了,主权没过去,这等于把大杂院挂了个"领域层"的门牌。
自检只看两个征兆。征兆一:领域类的方法数。聚合根上如果除了 getter 只有一个构造器,规则就没搬家。征兆二:应用层方法的行数。一个 execute 超过二十行、里面出现 if 判断金额之类的业务条件,就是剧本在演法典的戏。治理手段也直白:每发现一条规则写在应用层,就按第 3 章的判据问它"你操心谁的不变量",然后把搬家的活做掉。青柚商城的迁移持续了六周,没停机没专项立项,就是靠"规则搬家"这件小事持续磨。
问:表现层与应用层的分工怎么把握?我总忍不住在 Controller 里写 if。 答:判据看那个 if 判断的是什么。判断"参数格式对不对、登录态有没有"是表现层的事;判断"这笔订单能不能取消"是领域的事——后者哪怕一行也不该出现在 Controller,调 quoteCancellation(5.2 的例子)就行。Controller 允许长,但不允许懂业务。
问:事务边界该放在哪一层? 答:应用层,而且是它存在的最主要理由之一。一个用例方法开一个事务,领域方法内部不碰事务注解——领域对象被事务包裹是本末倒置:事务是用例的属性(这次操作要么成要么败),不是业务的属性(规则永远成立)。
楼盖好了,还差最关键的一根承重问题:层与层之间,依赖的箭头往哪指?4.2 依赖倒置与整洁架构。