一、先讲一个真实的困惑商品到底是什么在优选门店里问不同部门同一个词“商品”。商品管理部门说商品就是 SPU/SKU有标题、图片、类目、规格、品牌。库存部门说商品就是仓库里那个有数量、有库位的东西我不关心它图片好不好看。订单部门说商品就是下单那一刻的一个快照——名称、单价、数量成交后图片改了价格变了都跟这张订单无关。促销部门说商品就是一个能打折、能参加满减的计价单位。同一个词商品在四个部门里指的根本不是同一个东西。传统做法会怎么干建一张大而全的product表把所有部门要的字段都塞进去全公司共用一个Product类。结果这个Product类有 50 多个字段库存的同事看不懂一半。改商品图片字段结果订单模块的代码编译不过——因为大家共用一个类。谁都能改这个类谁都不敢改这个类。这就是没有限界上下文的下场。二、大白话限界上下文是什么限界上下文 一个明确的边界在这个边界内每个术语只有一个清晰、唯一的含义。界是边界限是限定。它限定了一套领域模型和它的术语的适用范围。回到商品的例子我们划出四个限界上下文每个上下文里的商品是不同的模型【商品上下文】 【库存上下文】 【订单上下文】 【促销上下文】 Product StockItem OrderItem PromotionItem - 标题/图片/类目 - SKU编号 - 商品名称(快照) - 计价单位 - 规格/品牌 - 可用数量 - 成交单价(快照) - 可打折标记 - 上架状态 - 库位 - 数量 - 锁定数量每个上下文里的模型只为自己上下文的职责服务互不干扰。库存上下文根本没有图片这个概念订单上下文里的价格是下单那一刻冻结的快照。类比同一个词苹果在水果店指水果在数码店指手机。水果店和数码店就是两个限界上下文。脱离上下文谈苹果是什么没有意义。三、原理图为什么一个大模型会失控正解边界按限界上下文拆模型模型隔离模型隔离模型隔离模型隔离8. 上下文建模一词一义9. 商品上下文Catalog.Product10. 库存上下文Inventory.StockItem11. 订单上下文Order.OrderItem12. 促销上下文PromotionItem反例边界全公司共享一个 Product字段追加字段追加字段追加字段追加耦合累积耦合累积耦合累积耦合累积关键风险1. 共享 Product万能模型2. 商品字段标题/图片/规格3. 库存字段SKU/库位/数量4. 订单字段成交快照5. 促销字段计价规则6. 字段膨胀模型失控7. 风险点谁都能改谁都不敢改新手要记住重复类名不一定是坏事共享错误模型才是坏事。Catalog.Product和Order.OrderItem看似都和商品有关但它们关心的业务事实不同就应该分开。四、统一语言Ubiquitous Language限界上下文的边界内要靠统一语言来保证术语一致。统一语言 在一个限界上下文内业务人员、产品、开发、测试以及代码本身全都用同一套术语且含义完全一致。这意味着产品说冻结优惠券代码里就该有coupon.freeze()而不是couponMapper.updateStatus(id, 2)。业务说订单待支付代码里订单状态就叫PENDING_PAYMENT不是status 0。需求文档、数据库表、Java 类名、方法名全部对齐同一套词汇。为什么这么做好处是什么消灭翻译损耗没有统一语言时业务说一套、文档写一套、代码又是一套每次需求都要在三套语言间翻译一翻译就丢信息、就出 bug。统一语言让业务怎么说代码就怎么写。代码自解释order.cancel()、coupon.freeze()这样的代码业务人员都能看个大概新人上手快。建模即沟通开会讨论术语的过程本身就是在澄清业务规则。很多业务理解上的分歧会在这个词到底什么意思的争论中暴露出来。对比传统做法的弊端传统项目里数据库字段叫f_status、type1、flag方法叫doProcess()、handle2()。业务语言和代码语言完全是两个世界。三个月后写代码的人自己都忘了status2是什么意思只能去翻代码、问人效率极低且极易出错。五、限界上下文 vs 子域到底啥关系新手最容易混。一句话区分子域是问题空间的划分业务上有哪些事要做限界上下文是解空间的划分软件上怎么建模和切分。更白话一点子域是业务地图限界上下文是代码地图。子域告诉你业务有哪些块限界上下文告诉你这些块在软件里怎么建模、怎么隔离。比如优选门店从业务上看有商品、库存、订单这些子域但落到代码里商品这个词不能全系统共用一个万能Product场景在这个上下文里商品真正关心什么商品上下文标题、图片、类目、规格、上下架状态库存上下文SKU、库位、可用库存、锁定库存订单上下文下单那一刻的商品名、成交价、购买数量所以再压缩成一句子域偏业务分类限界上下文偏模型边界。理想情况下一个子域对应一个限界上下文一一对应最清晰。但现实中也可能一个大子域拆成多个上下文历史遗留系统里一个上下文横跨了多个子域不理想但常见。对优选门店我们采用清晰的一一对应子域业务视角限界上下文软件视角订单交易核心订单上下文 Order Context促销核心促销上下文 Promotion Context商品支撑商品上下文 Catalog Context库存支撑库存上下文 Inventory Context会员支撑会员上下文 Member Context支付通用支付上下文 Payment Context配送支撑配送上下文 Delivery Context六、限界上下文怎么落到代码Spring Boot限界上下文不是抽象概念它直接决定你的工程结构。两种常见落地方式方式 A单体应用内用模块/包隔离推荐起步一个 Spring Boot 工程按上下文分顶层包包与包之间不允许随意互相调用com.youxuan.store ├── order # 订单上下文 │ ├── domain │ ├── application │ └── infrastructure ├── inventory # 库存上下文 ├── catalog # 商品上下文 ├── promotion # 促销上下文 ├── member # 会员上下文 └── payment # 支付上下文关键纪律order包里不直接importinventory包里的领域对象。要交互走第 04 章讲的上下文集成方式事件、防腐层、API。这叫模块化单体是迈向微服务前最稳妥的一步。方式 B每个上下文一个微服务当团队大了、上下文足够独立每个限界上下文可以独立成一个 Spring Boot 微服务各有各的数据库。重要原则限界上下文是微服务拆分的天然依据。不要按技术分层拆微服务“实体服务”“DAO 服务”要按限界上下文拆“订单服务”“库存服务”。这是 DDD 给微服务最大的贡献之一。建议除非团队和业务规模真的需要否则先用方式 A模块化单体。边界划清楚了将来拆微服务只是把包变成服务成本很低反过来边界没划清就拆微服务会得到一堆互相乱调的分布式大泥球比单体还难维护。七、怎么验证边界有没有被破坏最简单的验证是看import一个上下文不应该直接依赖另一个上下文的领域模型。# 例订单上下文不应该直接 import 库存上下文的 domain/modelgrep-Rimport .*inventory.*domainsrc/main/java/com/youxuan/store/order# 例查所有跨上下文 import人工判断是否应该改为事件/API/防腐层grep-Rimport .*\\.\\(order\\|inventory\\|catalog\\|promotion\\|member\\|payment\\)\\.src/main/java/com/youxuan/store如果团队使用 Spring Modulith可以加一个模块边界测试importorg.junit.jupiter.api.Test;importorg.springframework.modulith.core.ApplicationModules;classModularityTests{TestvoidverifiesModuleBoundaries(){ApplicationModules.of(StoreApplication.class).verify();}}这个测试会检查模块之间是否有循环依赖、是否访问了别的模块内部包。它不是 DDD 必需品但对新手团队很有帮助边界不是靠口头约定而是能被测试守住。八、容易踩的坑追求一个 Product 类全公司复用。这是反 DDD 的。宁可在不同上下文里有Product、StockItem、OrderItem三个看似重复的类也好过一个 50 字段的万能类。它们本来就是不同的东西。过早拆微服务。边界没想清楚就拆是灾难。先模块化单体。统一语言只停留在文档。统一语言必须落到代码类名、方法名、状态枚举才有意义。我们已经把优选门店切成了 7 个限界上下文。但它们不是孤岛——下单要扣库存、要加积分、要发起支付。上下文之间怎么对接而不重新耦合成一团这就是下一章上下文映射的内容。