从“深夜食堂”到“架构食堂”:用做菜搞懂DDD分层架构
·
🍳 从“深夜食堂”到“架构食堂”:用做菜搞懂DDD分层架构
假设我们要开发一个智能点餐系统,用户可以从外卖小哥的夺命连环call中解脱出来,优雅地在线下单。让我们看看这个系统的“后厨”是如何运作的!
🎯 系统背景:智能点餐系统
用户想点一份东坡肘子PLUS版(普通版不够吃),系统需要:
- 创建订单
- 检查用户余额
- 通知后厨做菜
- 告诉用户“别急,菜在路上”
- 如果用户余额不足,直接劝他“吃点素更健康”
🏗️ 我们的厨房(架构)长这样
🍽️ customer/ (顾客区域)
├── api/ # 接待员小姐姐
├── trigger/ # 外卖骑手/电话订单
├── app/ # 大堂经理
├── domain/ # 主厨和他的秘方
├── infrastructure/ # 厨房设备供应商
└── types/ # 通用菜单术语
👩💼 1. api/ - 前台接待员小姐姐 💁♀️
职责:笑脸迎人,接单收钱,但绝不进厨房!
// api/controllers/OrderController.java
@RestController
public class OrderController {
@PostMapping("/orders")
public OrderResponse createOrder(@RequestBody CreateOrderRequest request) {
// 她只负责:
// 1. 检查顾客有没有穿拖鞋(参数校验)
// 2. 把订单递给后面的经理
// 3. 绝不问“肘子要几分熟”这种专业问题!
return orderAppService.createOrder(request);
}
}
她手里的菜单:
CreateOrderRequest.java- 顾客填的点菜单OrderResponse.java- 给顾客的回执- 特点:颜值高,说话好听,但业务?不懂不懂!
📞 2. trigger/ - 多渠道接单员
职责:不仅接待堂食,还得接电话订单、外卖平台订单!
// trigger/listeners/PhoneOrderListener.java
@Component
public class PhoneOrderListener {
// 电话响了!“我要订餐!”
public void handlePhoneOrder(PhoneOrderMessage message) {
orderAppService.createOrder(convert(message));
}
}
// trigger/jobs/CouponExpireJob.java
// 定时任务:凌晨2点,把过期优惠券统统“枪毙”
他的设备:
- 电话☎️、外卖APP📱、定时闹钟⏰
- 特点:哪里有点单声,哪里就有他!
👔 3. app/ - 大堂经理
职责:协调各方,但不拿锅铲!
// app/services/OrderAppService.java
@Service
public class OrderAppService {
public OrderResponse createOrder(CreateOrderRequest request) {
// 1. 检查顾客是不是VIP(权限校验)
if (!userRepository.isVip(request.getUserId())) {
throw new NotVipException("亲,充个会员再点肘子?");
}
// 2. 问主厨:这单能接不?
Order order = orderDomainService.createOrder(request.toCommand());
// 3. 通知后厨:开工啦!
kitchenTrigger.notifyCook(order);
// 4. 返回标准回执
return OrderResponse.from(order);
}
}
他的特点:
- 不关心“肘子怎么做”,只关心“谁能做肘子”
- 事务由他控制:要么全上菜,要么全取消
- 经典语录:“王师傅,3号桌的肘子好了没?客人在催了!”
👨🍳 4. domain/ - 主厨和秘方 🧑🍳
这才是核心!米其林三星的秘密都在这里!
// domain/models/Order.java
public class Order {
private OrderId id;
private Money totalAmount;
private OrderStatus status;
private List<OrderItem> items;
// 业务规则1:点肘子必须配米饭!
public void addItem(FoodItem item) {
if (item.isPorkKnuckle() && !containsRice()) {
throw new BusinessException("光吃肉不吃饭,腻不死你!");
}
this.items.add(item);
}
// 业务规则2:总价不能超过余额
public void charge(User user) {
if (totalAmount.greaterThan(user.getBalance())) {
throw new InsufficientBalanceException("余额不足,建议点素菜");
}
user.deductBalance(totalAmount);
}
}
// domain/services/OrderDomainService.java
// 跨实体的复杂逻辑:检查库存、计算优惠、生成订单号...
他的领地:
- 实体:
Order、User、FoodItem- 后厨的“核心食材” - 值对象:
Money、Address- 没有ID的“调味料” - 领域服务:复杂的“烹饪流程”
- 领域事件:
OrderCreatedEvent- “肘子订单已下达!” - 仓储接口:
OrderRepository- 只说“我要存取订单”,不说“怎么存” - 特点:纯粹的业务! 不知道数据存在MySQL还是Excel,也不知道谁在调用他!
🔧 5. infrastructure/ - 厨房设备供应商 🛠️
职责:给主厨提供趁手的工具!
// infrastructure/persistence/OrderRepositoryImpl.java
@Repository
public class OrderRepositoryImpl implements OrderRepository {
// 实现domain定义的接口
@Override
public Order save(Order order) {
// 我知道数据存在MySQL里!
OrderPO po = convertToPO(order);
orderDao.save(po);
return convertToDomain(po);
}
}
// infrastructure/external/KitchenClientImpl.java
// 调用后厨系统的API:”王师傅,开工啦!“
他的工具箱:
- 数据库:MySQL、Redis
- 消息队列:Kafka、RabbitMQ
- 外部API调用:支付、短信、后厨系统
- 配置文件:
application.yml- 火候控制参数! - 特点:技术达人,但业务?那是主厨的事!
📖 6. types/ - 通用菜单术语
职责:避免“你说微辣,他说变态辣”的沟通问题!
// types/enums/OrderStatus.java
public enum OrderStatus {
CREATED, // 已下单
PAID, // 已付款
COOKING, // 烹饪中
DELIVERING, // 配送中
COMPLETED, // 已完成
CANCELLED // 已取消
}
// types/constants/SystemConstants.java
public class SystemConstants {
public static final int MAX_ORDER_ITEMS = 20; // 最多点20个菜
}
// types/exceptions/BusinessException.java
// 通用的业务异常
他的笔记:
- 所有人都会用的枚举、常量、异常、基础DTO
- 特点:共享词典,防止各说各话!
🔄 完整订单流程(看看他们怎么协作)
具体对话:
顾客:”我要东坡肘子!“
api接待员:”好的亲,这是您的点菜单!“
app经理:”VIP顾客,肘子...主厨看看能做不?“
domain主厨:”肘子可以,但得配米饭!规则写的明明白白!“
infra小哥:”订单存好了!后厨,接单!“
💡 记住这个“厨房法则”
| 角色 | 相当于 | 能做什么 | 不能做什么 |
|---|---|---|---|
| api | 接待员 | 接单、微笑 | 进厨房、改菜单 |
| app | 大堂经理 | 协调、催菜 | 动手炒菜 |
| domain | 主厨 | 定规则、管质量 | 关心用什么锅 |
| infra | 设备商 | 提供锅碗瓢盆 | 规定菜怎么炒 |
| trigger | 外卖接单员 | 多渠道接单 | 处理业务逻辑 |
| types | 通用菜单 | 统一术语 | 实现具体逻辑 |
🎭 幽默小剧场
错误示范1 - api层想当主厨:
// ❌ 大忌!接待员冲进厨房了!
@RestController
public class BadController {
@PostMapping("/order")
public void createOrder() {
// 直接在Controller里查数据库、算价格
User user = userRepository.findById(userId); // 不要啊!
if (order.getAmount() > 100) { // 业务规则跑这儿来了!
// 主厨在厕所哭晕...
}
}
}
错误示范2 - 主厨关心用什么锅:
// ❌ 主厨不务正业!
public class BadOrder {
public void save() {
// 主厨居然在考虑用MySQL还是MongoDB?
String sql = "INSERT INTO orders..."; // 放下那个SQL!
}
}
🏆 总结:DDD分层的核心思想
-
domain是皇上 👑
- 拥有最终解释权(业务规则)
- 谁都不能动他的规矩
-
其他人都是打工人 💼
api:御前侍卫 - 只传话,不决策app:内阁首辅 - 协调各方,执行皇命infra:工部尚书 - 提供工具,但不干政trigger:驿丞 - 传递八方消息types:翰林院 - 统一文书格式
-
依赖关系铁律 ⛓️
- 皇上的规矩(domain)不依赖任何打工仔
- 打工仔都得听皇上的
- 方向单一:
api→app→domain←infra
🍰 最后的甜点
下次写代码时,问问自己:
- 我在当接待员(参数校验)?
- 还是大堂经理(协调事务)?
- 还是主厨(制定业务规则)?
记住:让主厨安心炒菜,让接待员专心微笑,让经理好好协调——你的系统就能像米其林三星后厨一样,高效、优雅、不出乱子!
系统稳定秘诀:各司其职,不要越俎代庖。除非…你想看到接待员拿着锅铲,主厨在门口迎宾的魔幻场面!🔥
文档作者:一个被DDD折磨过也深爱着的开发者
灵感来源:无数个加班写代码的深夜,幻想自己是在米其林厨房 🍳
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)