后端开发怎么用 DDD 划分微服务边界

liulian
liulian 初级会员超兽战士
发布于 2026-10-08 22:24 ·1 浏览 ·0 回复

用 DDD 划分微服务边界,核心不是先画服务图,而是先通过事件风暴找出限界上下文,再让一个限界上下文默认对应一个微服务;聚合只在单个微服务内保持强一致,跨上下文用领域事件和最终一致。按技术层拆、按数据库表拆、按实体拆,都会做出分布式单体。

DDD 划分微服务边界的第一步是什么?

结论:第一步是事件风暴,不是画架构图。找 4-8 个业务专家和开发,用 2 天工作坊,按时间轴贴出 20-50 个领域事件,再补命令、聚合和限界上下文,最终得到 5-10 个候选上下文。

具体做法:先贴“已发生的事实”,用过去式命名,例如 OrderPlaced、InventoryReserved、PaymentCompleted。再往前补命令:PlaceOrder、ReserveInventory、PayOrder。然后把强一致修改的一组命令和事件圈成一个聚合,例如订单聚合包含 Order 和 OrderItem。最后把同一业务语言、同一数据所有权、同一业务目标的聚合归成一个限界上下文,例如订单上下文、库存上下文、支付上下文、物流上下文。

判断标准很直接:如果两个团队对“商品”这个词的理解不同,它们大概率不在同一个限界上下文。订单里的商品是价格快照,库存里的商品是 SKU 和可售数量,支付里的商品是金额明细。语言不同,边界就出来了。

限界上下文和微服务是一一对应吗?

结论:默认一个限界上下文对应一个微服务,但允许三种例外:团队只有 5 人以下时,可先做模块化单体;两个上下文共享同一发布节奏和同一数据生命周期时,可先合在一个服务;非核心域上下文可外购或合并。

不要反过来做:一个微服务里塞多个语言不通的上下文,半年后代码会出现两套命名;一个上下文拆成多个微服务,聚合根会被迫跨服务调用,强一致变成分布式事务。经验值是:一个微服务对应一个团队,团队规模 5-9 人,服务独立部署、独立数据库、独立监控。

怎么判断两个功能该不该拆成两个微服务?

结论:满足“不同业务能力、不同语言、不同数据所有权、不同发布节奏、不同伸缩需求、不同团队负责”中的 3 条以上,才拆;如果共享聚合根且需要强一致,不拆。

拿电商举例:订单和库存该拆,因为订单要保存历史价格,库存要扣减可售数量,数据所有权不同;订单和支付该拆,因为支付有对账、退款、渠道回调,发布频率高于订单;订单和物流该拆,因为物流依赖承运商,伸缩曲线不同。反例:订单和订单明细不拆,因为它们在同一聚合内,必须同事务写入。

拆完后只允许通过 ID 引用,禁止跨服务联表。订单服务存 sku_id,不存库存数量;库存服务存 sku_id 和 available_qty,不存订单价格。

聚合根、领域事件怎么影响边界?

结论:一个事务只修改一个聚合;跨聚合、跨上下文用领域事件做最终一致。聚合是微服务边界内的最小强一致单元,聚合根 ID 就是跨服务引用的唯一钥匙。

示例流程:下单时,订单服务在本地事务里写入 orders、order_items,同时写入 outbox 表,字段包括 event_id、aggregate_id、event_type、payload、occurred_at、published。然后用 Debezium 或定时任务把 outbox 发到 Kafka,topic 命名 order.order-placed.v1。库存服务订阅后扣减库存,发 InventoryReserved;支付服务订阅后发起支付,发 PaymentCompleted。

消费者必须幂等,用 event_id 去重。事件字段至少包含 event_id、occurred_at、aggregate_id、version。不要用“订单服务直接 update 库存表”的方式,那会把两个上下文重新焊死。

上下文映射有哪些模式,怎么选?

结论:核心域用防腐层隔离外部模型;上游稳定且强势用遵奉者;上游可协商用客户-供应商;对外集成用开放主机服务加发布语言。

常见模式包括:防腐层,放在调用方服务里做 DTO 转换,禁止外部模型进入核心域;共享内核,只适合两个团队同步发布,生产环境少用;客户-供应商,下游能提需求,上游排期;遵奉者,下游直接接受上游模型;开放主机服务,上游提供统一 API;发布语言,用 JSON Schema 或 Protobuf 定义事件契约;分道扬镳,两边完全独立。

选法:支付上下文是核心域,接入微信、支付宝时用防腐层;物流上下文是支撑域,直接遵奉承运商模型;订单对外发布事件,用发布语言固定 schema,版本号写进 topic,例如 order.order-placed.v1。

落地时数据库和 API 怎么分?

结论:每个微服务独立数据库或独立 schema,禁止跨服务联表;跨上下文只走 API 或事件。一个限界上下文一个数据库,一个聚合一个事务,一个服务一个团队。

API 按业务能力设计,不要暴露贫血 CRUD。订单服务提供 POST /v1/orders、POST /v1/orders/{id}/cancel;库存服务提供 POST /v1/inventory/reservations。事件契约单独版本化。部署上,一个服务一个 Kubernetes Deployment,独立扩缩容。监控按服务设 SLI,例如订单创建 P99 小于 300ms,库存扣减成功率大于 99.95%。

常见误区有哪些?

结论:按技术层拆、按实体拆、共享数据库、拆完必须一起发布,都是分布式单体的信号。技术层拆会得到用户服务、订单服务、日志服务,边界和业务无关;按实体拆会得到 UserService 被所有服务调用,变成中心瓶颈;共享数据库会让任何表结构变更同时影响多个服务。

如果两个微服务每次上线都必须一起发版,说明边界画错了。正确回退方式是先合并成一个模块化单体,按限界上下文分模块,模块间只走接口和事件,等发布节奏真正独立后再拆成微服务。

收束:DDD 划分微服务边界,先事件风暴找限界上下文,再一上下文一服务;聚合内强一致,跨上下文领域事件最终一致;数据库和 API 按上下文隔离。拆不拆,看语言、数据所有权、发布节奏和团队边界,不看代码目录。

版权声明:本文来自 GJ站长论坛《后端开发怎么用 DDD 划分微服务边界》
原文链接:https://www.gj0.com/thread-1226.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~