Java DDD(领域驱动设计)

📅 2026/7/24 1:16:38 👁️ 阅读次数 📝 编程学习
Java DDD(领域驱动设计)

一句话核心:DDD(领域驱动设计)不是一套框架,而是一种代码组织哲学。它要求你用代码直接翻译业务语言,而不是把业务逻辑散落在增删改查(CRUD)里。

对于Java开发者,最经典的DDD范式就是**“四层架构” + “六大战术模式”。为了让你秒懂,我们把项目类比为“一家餐厅”**:

  • 表现层(Controller)= 服务员(接收顾客指令)。
  • 应用层(Application)= 大堂经理(调度任务,发号施令)。
  • 领域层(Domain)= 厨师长 + 菜谱(核心资产,业务规则所在)。
  • 基础设施层(Infrastructure)= 冰箱、灶台(数据库、Redis、MQ)。

下面我直接给你展示标准的Java DDD包结构核心代码范式,一看就懂。


1. 标准的Java DDD包结构(范式骨架)

com.example.order/ ├── interfaces/ # 表现层(处理HTTP/RPC) │ └── OrderController.java ├── application/ # 应用层(用例编排,无业务逻辑) │ ├── service/ # 应用服务(如:OrderApplicationService) │ └── dto/ # 数据传输对象(Request/Response) ├── domain/ # 领域层(核心!只依赖Java本身,不依赖Spring/DB) │ ├── model/ # 领域模型 │ │ ├── aggregate/ # 聚合根(如:Order) │ │ ├── entity/ # 实体(如:OrderItem) │ │ └── vo/ # 值对象(如:Address, Money) │ ├── service/ # 领域服务(处理跨多个实体的复杂业务) │ └── repository/ # 仓储接口(定义“我需要什么数据”,不实现) └── infrastructure/ # 基础设施层(实现Domain层的接口) ├── repository/ # 仓储实现(JPA/MyBatis) └── config/ # 配置类

2. 六大战术模式(Java代码实战)

为了便于理解,我们模拟一个**“下单”**场景:必须验证地址,且订单总价要打折。

① 值对象 (Value Object) —— 用record或不可变类

特点:没有唯一ID,通过属性值判断相等性。封装业务规则

// 值对象:地址publicrecordAddress(Stringprovince,Stringcity,Stringdetail){publicAddress{// 业务规则:地址不能为空,直接写在构造方法里if(province==null||city==null){thrownewIllegalArgumentException("省市必填");}}}// 值对象:金额publicclassMoney{privatefinalBigDecimalamount;privatefinalStringcurrency;// 通常用枚举// 封装加法逻辑,而不是在Service里用BigDecimal算publicMoneyadd(Moneyother){...}}
② 实体 (Entity) —— 有唯一ID,可变
@Entity// 注意:这里的注解是JPA的,但DDD要求Domain层纯POJO,所以通常把这个类放在infrastructure层做映射,或者在Domain层不加@Table注解。publicclassOrderItem{privateLongitemId;// 唯一标识privateStringproductName;privateIntegerquantity;privateMoneyprice;// 引用值对象}
③ 聚合根 (Aggregate Root) ——DDD最重要的范式

聚合根是外部访问聚合的唯一入口。比如“订单(Order)”和“订单项(OrderItem)”是整体,外部只能操作Order,不能直接修改OrderItem

// 领域层 - 聚合根publicclassOrder{privateLongorderId;// 唯一标识privateAddressshipAddress;// 值对象privateList<OrderItem>items;// 实体集合privateOrderStatusstatus;// 核心业务方法(充血模型):业务逻辑写在实体内部,而不是Service里!publicvoidplaceOrder(){// 规则1:下单必须校验地址(封装在领域内部)if(this.shipAddress==null){thrownewBusinessException("缺少收货地址");}// 规则2:计算总价并打折(满100减10)Moneytotal=calculateTotal();if(total.amount().compareTo(newBigDecimal("100"))>=0){// 打折逻辑...}this.status=OrderStatus.PLACED;// 发布领域事件(可选)DomainEventPublisher.publish(newOrderPlacedEvent(this.orderId));}// 计算总价也不允许Service插手,由实体自己算privateMoneycalculateTotal(){returnitems.stream().map(OrderItem::getPrice).reduce(Money::add).orElse(...);}}
④ 仓储接口 (Repository) ——依赖倒置的核心

Domain层定义“我要什么”,Infrastructure层实现“怎么拿”

// domain/repository/OrderRepository.java (接口)publicinterfaceOrderRepository{OrderfindById(Longid);voidsave(Orderorder);// 传入聚合根,整个聚合一起保存}// infrastructure/repository/OrderRepositoryImpl.java (实现)@RepositorypublicclassOrderRepositoryImplimplementsOrderRepository{@AutowiredprivateOrderJpaMappermapper;// MyBatis或JPA@Overridepublicvoidsave(Orderorder){// 1. 保存Order主表// 2. 保存OrderItem子表(全部替换策略)// 领域层不知道底层是MySQL还是Redis}}
⑤ 领域服务 (Domain Service)

当业务逻辑不属于某个具体的实体时使用(比如转账涉及两个账户)。

// domain/service/TransferService.javapublicclassTransferService{publicvoidtransfer(Accountfrom,Accountto,Moneyamount){from.debit(amount);// 实体自己的方法to.credit(amount);}}
⑥ 应用层 (Application Service) ——极薄的一层

只做三件事:拿数据、调领域方法、存数据。绝对不写 if-else 业务判断

// application/service/OrderAppService.java@Service@TransactionalpublicclassOrderAppService{privatefinalOrderRepositoryorderRepository;// 用例:用户下单publicLongplaceOrder(PlaceOrderCommandcmd){// 1. 从仓储拿聚合根(如果是新建则new)Orderorder=newOrder();// 2. 调用领域方法(业务逻辑进入领域层)order.placeOrder();// 3. 仓储保存orderRepository.save(order);// 4. 返回结果returnorder.getOrderId();}}

3. 必须遵守的“铁律”(DDD Java范式红线)

层级是否允许依赖Spring注解是否允许依赖数据库职责
领域层 (Domain)❌ 禁止(纯Java POJO)❌ 禁止只写interface定义规则,写业务逻辑方法
应用层 (Application)✅ 允许 (@Service)❌ 禁止编排用例(调用领域方法),事务发起点
基础设施层 (Infra)✅ 允许 (@Repository)✅ 允许实现接口,处理SQL、Redis、MQ
表现层 (Interfaces)✅ 允许 (@RestController)❌ 禁止参数校验、返回JSON

为什么领域层禁止用Spring注解?
因为DDD追求**“业务逻辑与技术解耦”**。如果@Autowired进了Domain,换用Quarkus或单元测试时就废了。Domain应该是可以脱离Spring,直接用new Order().placeOrder()就能跑单元测试的。


4. 贫血模型 vs 充血模型(一眼辨别真假DDD)

  • 传统CRUD(贫血)Order类只有getter/setter,所有if/else全在OrderServiceImpl里。→这是假DDD
  • DDD范式(充血)Order类拥有placeOrder()cancel()calculateTotal()方法。OrderAppService只有寥寥几行调用。→这是真DDD

总结(记住这张脑图)

表现层接收请求 →应用层基础设施层拿到仓储接口的实现 → 交给领域层聚合根执行业务方法(内部包含实体和值对象的规则) → 应用层再通过仓储存回去。

如果只是简单的增删改查,请不要用DDD(杀鸡用牛刀)。但如果业务逻辑极其复杂(如电商、金融、医疗),这个Java范式就是防止代码腐烂的最强武器。记住核心口诀:“让代码说业务的话,让数据库滚去基础设施层”