前言

做这个下午茶拼单系统时,我最先想清楚的不是接口怎么写,而是它到底要解决什么问题。场景很具体:写字楼、校园或者团队下午茶,用户围绕奶茶、咖啡、甜点发起拼单,系统要覆盖优惠试算、营销锁单、支付、组队结算、逆向退单等流程。简历里写成一句话很短,但真正落到后端,里面有活动、人群、交易三个完全不同的变化方向。

我参考 MYDB 系列文章的写法,把过程拆成一组开发笔记。MYDB 是从底层模块一步步往上搭,这个项目也类似:先搭 DDD 分层,再做规则树试算,然后处理优惠策略、Redis 标签、动态配置、交易锁单、MQ 回调和部署复盘。不同的是,拼单系统更偏业务复杂度和高并发链路,而不是单机存储结构。

为什么先拆领域

如果把所有逻辑都塞进一个 OrderService,最开始会很快,后面会非常难改。活动规则、用户标签、交易订单、支付回调、库存可见性这些事情看起来都和“下单”相关,但变化原因不同。DDD 的价值就在这里:不是为了显得架构很重,而是把变化点隔开。

我把项目拆成营销拼团域和交易支付域。营销域负责活动配置、优惠试算、人群控制、锁单结算;交易域负责订单、支付、退款和用户订单列表。两个域之间通过端口和网关交互,交易域不直接关心营销规则细节,只关心锁单后返回的优惠金额和 teamId。

public interface IProductPort {
    ProductEntity queryProductByProductId(String productId);

    MarketPayDiscountEntity lockMarketPayOrder(
            String userId, String teamId, Long activityId,
            String productId, String orderId);

    void settlementMarketPayOrder(String userId, String orderId, Date orderTime);

    void refundMarketPayOrder(String userId, String orderId);
}

这段端口定义把交易侧和营销侧隔离开了。交易订单只需要知道“我要锁一笔营销单”“我要结算”“我要退单”,至于拼团活动是否开启、活动规则如何算、人群标签怎么查,都留给营销域处理。

两条主链路

第一条是营销试算链路:用户进入商品详情页时,系统根据 source、channel、goodsId、userId 查询活动配置、商品价格和用户标签,计算出是否可见、是否可参与、优惠后价格。这个链路直接影响页面打开速度,所以后面会引入 FutureTask 和线程池做并行查询。

第二条是交易链路:用户选择拼团下单,交易域创建本地订单,调用营销域锁定拼团资格和优惠金额,再创建支付宝支付单。支付成功后,交易域触发营销结算,拼团成功再通过 HTTP 或 RabbitMQ 回调交易域做最终状态变更。这里的关键词不是“同步调用”,而是最终一致性。

工程结构

项目使用 Spring Boot、MyBatis、MySQL、Redis、RabbitMQ 等技术栈,代码按 api、app、domain、infrastructure、trigger、types 分层。api 层定义对外契约,domain 层承载业务模型,infrastructure 适配数据库、Redis 和外部 HTTP,trigger 层放 Controller、Job、Listener。

group-buy-market
├── group-buy-market-api
├── group-buy-market-app
├── group-buy-market-domain
├── group-buy-market-infrastructure
├── group-buy-market-trigger
└── group-buy-market-types

s-pay-mall-ddd-market
├── s-pay-mall-ddd-api
├── s-pay-mall-ddd-app
├── s-pay-mall-ddd-domain
├── s-pay-mall-ddd-infrastructure
├── s-pay-mall-ddd-trigger
└── s-pay-mall-ddd-types

这样的目录看起来文件多,但每个包的职责相对清楚。比如 Redis BitMap 的实现放在 infrastructure,规则树节点放在 domain,HTTP/RabbitMQ 回调放在 trigger。写文章复盘时,我也按这个分层顺序展开,避免只讲 Controller 到 Mapper 的 CRUD。

从简历落到代码

简历里提到的几个关键词,分别对应后续文章的重点:DDD 领域驱动设计对应工程拆分;规则树和责任链对应营销试算流程;FutureTask 和线程池对应并行加载活动与商品数据;Redis 发布订阅和 DCC 对应动态配置;Redis BitMap 对应人群标签与库存可见性;RabbitMQ 和 HTTP 双通道对应支付后的异步回调与消息可靠性。

我希望这组文章看起来像真实开发过程,而不是把技术名词堆在一起。每一篇都围绕一个具体问题:接口慢了怎么办、规则变化怎么办、用户标签怎么存、锁单失败怎么补偿、回调重复怎么处理。这样写出来的项目经历,在面试里也更容易展开。

小结

第一篇先把边界划清楚。下午茶拼单系统不是一个简单的下单页,而是营销和交易协作的系统。后面几篇会从营销试算开始,一层层展开到支付、结算、退单和部署。和 MYDB 一样,先有整体结构,后面每个模块才有落点。