🍳 从“深夜食堂”到“架构食堂”:用做菜搞懂DDD分层架构

假设我们要开发一个智能点餐系统,用户可以从外卖小哥的夺命连环call中解脱出来,优雅地在线下单。让我们看看这个系统的“后厨”是如何运作的!

🎯 系统背景:智能点餐系统

用户想点一份东坡肘子PLUS版(普通版不够吃),系统需要:

  1. 创建订单
  2. 检查用户余额
  3. 通知后厨做菜
  4. 告诉用户“别急,菜在路上”
  5. 如果用户余额不足,直接劝他“吃点素更健康”

🏗️ 我们的厨房(架构)长这样

🍽️  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
// 跨实体的复杂逻辑:检查库存、计算优惠、生成订单号...

他的领地

  • 实体OrderUserFoodItem - 后厨的“核心食材”
  • 值对象MoneyAddress - 没有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: 大堂经理协调

domain: 主厨检查规则

domain: 创建订单实体

infra: 存到数据库

infra: 通知后厨

完成!

具体对话

顾客:”我要东坡肘子!“
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分层的核心思想

  1. domain是皇上 👑

    • 拥有最终解释权(业务规则)
    • 谁都不能动他的规矩
  2. 其他人都是打工人 💼

    • api:御前侍卫 - 只传话,不决策
    • app:内阁首辅 - 协调各方,执行皇命
    • infra:工部尚书 - 提供工具,但不干政
    • trigger:驿丞 - 传递八方消息
    • types:翰林院 - 统一文书格式
  3. 依赖关系铁律 ⛓️

    • 皇上的规矩(domain)不依赖任何打工仔
    • 打工仔都得听皇上的
    • 方向单一:apiappdomaininfra

🍰 最后的甜点

下次写代码时,问问自己:

  • 我在当接待员(参数校验)?
  • 还是大堂经理(协调事务)?
  • 还是主厨(制定业务规则)?

记住:让主厨安心炒菜,让接待员专心微笑,让经理好好协调——你的系统就能像米其林三星后厨一样,高效、优雅、不出乱子!

系统稳定秘诀:各司其职,不要越俎代庖。除非…你想看到接待员拿着锅铲,主厨在门口迎宾的魔幻场面!🔥


文档作者:一个被DDD折磨过也深爱着的开发者
灵感来源:无数个加班写代码的深夜,幻想自己是在米其林厨房 🍳

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐