1. 领域建模最大的痛点不是设计不出来而是没法验证做后端开发的读者几乎都经历过这样一个场景项目启动时架构师带着团队做了几轮领域建模画了一堆 UML 图定义了实体、值对象、聚合根甚至把限界上下文都划分好了。半年后再看代码发现领域模型早就变了形——本该是值对象的地方变成了被到处修改的实体聚合根里塞满了基础设施相关的逻辑领域服务越写越像事务脚本依赖方向也开始乱掉。这不是个别团队的问题而是领域驱动设计落地时普遍存在的困境领域模型在文档里是清晰的在代码里却很难保持清晰。我们缺少的并不是建模能力而是一种能够随时验证“模型是否还正确”的机制。所以当我看到“可验证领域模型能力扩展无上限”这个命题时我的判断是这句话的关键词不是“扩展无上限”而是“可验证”。只有当一个领域模型可以被验证、被检查、被测试它才具备持续扩展的前提。否则每一次扩展都是在给未来的重构埋雷。这篇文章想围绕三个问题展开领域模型的“可验证”具体验证什么如何把领域模型的可验证性落到工程实践中在可验证的前提下领域模型的能力扩展为什么可以做到“无上限”对正在做 DDD 改造、接手复杂业务系统、或者准备搭建中台架构的读者来说这篇文章会给你一套可以直接落地的验证思路和代码级实践。2. “可验证领域模型能力扩展无上限”到底在说什么先把这句话拆开看。领域模型Domain Model是业务逻辑在代码中的结构化表达。它不是一个类、一张表而是一组对业务规则的建模包括实体、值对象、聚合、领域服务、领域事件等元素以及它们之间的联系与约束。领域模型的本质是让复杂的业务规则以代码的形式被显式表达而不是散落在 Service 层的 if-else 里。可验证Verifiable是指领域模型的正确性和边界能够被自动检查。这种检查至少包含四个层面模型结构是否正确聚合边界是否清晰、依赖方向是否正确业务不变量是否被满足金额不能为负、状态流转是否合法行为结果是否符合预期特定输入能否产生正确的领域事件模型在演进过程中是否发生漂移新加的代码是否破坏了原有约束。能力扩展无上限Unlimited Extensibility则是一种理想状态当模型的边界被验证规则明确框定后新增业务需求的成本不再随着系统复杂度指数上涨而是趋近于“在正确位置添加正确模型”的线性成本。这三个词放在一起逻辑就通了领域模型通过显式的规则被验证验证通过后模型具备了稳定的边界而稳定的边界恰恰是无限扩展的前提。用工程类比来解释一个函数如果没有入参校验、没有返回值约束、没有单元测试就不敢放心地让其他模块调用它。反之一个接口契约清晰、测试覆盖完善、行为可预测的函数反而可以放心地扩展调用方不需要关心内部实现。领域模型的可验证性本质上就是把“函数级”的契约思维放大到“模型级”。3. 为什么很多领域模型项目越改越乱3.1 模型和实现脱节文档是文档代码是代码很多团队画了漂亮的领域模型图但代码实现却完全不受图约束。模型的聚合边界在文档里代码里的类关系却是另一套。新同学拿到文档后无法通过代码反推模型只能“照着现有代码写”。时间一长模型图成了没人维护的摆设代码成了真正的“事实标准”。3.2 业务规则没有显式化散落在 Service 层订单金额的计算、状态流转的合法性判断、库存扣减的约束这些核心业务规则如果不放进领域模型而是散落在 Application Service 层就会出现一个很尴尬的局面同一个规则在多个地方各写一遍有些地方遵循了有些地方漏掉了。此时你无法验证模型是否正确因为规则根本没有归属于模型。3.3 依赖方向失控领域层被基础设施绑架DDD 分层架构要求领域层不依赖基础设施层依赖方向是从外层指向内层。但实际项目中领域对象往往被直接注入 Mapper、RedisTemplate、甚至第三方 SDK。一旦领域层依赖了基础设施模型就失去了纯粹性测试成本急剧上升扩展也变得极其危险——因为你不知道哪次改动会同时牵连数据库和缓存。这三个问题共同指向一个结论不是领域模型这个概念出了问题而是我们缺少一套验证体系让领域模型始终保持在正确的轨道上。4. 可验证领域模型的四个支柱要构建一套可验证的领域模型体系我认为至少需要四个支柱。第一个支柱模型规则显式化。领域模型里的业务不变量、状态流转规则、聚合边界不能只停留在文档里必须通过代码表达。具体方式包括在聚合根中实现不变量校验方法、使用值对象封装业务约束、用状态模式或状态机管理状态流转。第二个支柱架构守护自动化。依靠代码评审来检查依赖方向效率低且容易漏掉。更可靠的方式是使用自动化检查工具把架构约束写成测试集成到 CI 流程中。每次提交代码自动检查领域层是否违反依赖规则、是否引用了基础设施类。第三个支柱领域行为测试化。领域模型的行为必须通过测试固定下来。每次修改模型通过回归测试确认已有业务规则没有被破坏。这里的测试不是基于 Spring 的集成测试而是不依赖外部环境的纯单元测试对象直接 new 出来速度快、定位准。第四个支柱模型巡检可视化。当模型数量变多之后需要一种方式快速了解模型全貌。通过脚本或工具扫描模型代码输出聚合、实体、值对象的数量依赖关系以及潜在的坏味道如实体过大、依赖混乱形成巡检报告。这四个支柱的关系是递进的规则显式化解决“模型是否正确”的问题架构守护解决“模型是否被破坏”的问题行为测试解决“行为是否漂移”的问题可视化解解决“模型演进是否可控”的问题。四个支柱共同作用才可能让领域模型在扩展时依然保持稳定。5. 环境准备与技术选型下面的实践示例基于 Java 17 和 Spring Boot 3.x构建工具使用 Maven。之所以选择 Java 生态是因为 DDD 在 Java 社区有着比较成熟的工具链支撑包括架构守护、模型扫描等方面都有现成方案。说明一下这里不会把版本号写死实际项目请以当前稳定版本为准。核心验证思路不受具体版本影响。工程目录结构建议采用标准的 DDD 分层order-domain/ ├── pom.xml └── src └── main └── java └── com └── example └── order ├── OrderApplication.java ├── application │ └── service │ └── OrderApplicationService.java ├── domain │ ├── model │ │ ├── Order.java │ │ ├── OrderItem.java │ │ ├── OrderStatus.java │ │ └── Money.java │ ├── service │ │ └── OrderDomainService.java │ └── event │ └── OrderCreatedEvent.java ├── infrastructure │ ├── persistence │ │ └── OrderRepositoryImpl.java │ └── external │ └── StockServiceClient.java └── interfaces └── controller └── OrderController.java这是最经典的 DDD 四层结构interfaces 层接收请求application 层编排用例domain 层存放核心逻辑infrastructure 层提供技术支撑。本文的重点验证对象是domain包也就是 domain/model、domain/service、domain/event 三个子包。6. 核心代码实现订单模型的验证与扩展6.1 定义领域模型及业务不变量我们以一个订单模型为例。订单包含多个订单项每个订单项包含商品、数量和价格。订单金额等于所有订单项金额之和订单状态只能按照固定流程流转。先定义值对象Money// 文件路径order-domain/src/main/java/com/example/order/domain/model/Money.java package com.example.order.domain.model; import java.math.BigDecimal; import java.util.Objects; /** * 金额值对象。 * 金额不允许为负数所有金额运算必须经过本对象。 */ public final class Money { private final BigDecimal amount; private Money(BigDecimal amount) { if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalArgumentException(金额不能为负数); } this.amount amount; } public static Money of(BigDecimal amount) { return new Money(amount); } public static Money zero() { return new Money(BigDecimal.ZERO); } public Money add(Money other) { return new Money(this.amount.add(other.amount)); } public Money multiply(int quantity) { return new Money(this.amount.multiply(BigDecimal.valueOf(quantity))); } public BigDecimal getAmount() { return amount; } Override public boolean equals(Object o) { if (this o) { return true; } if (o null || getClass() ! o.getClass()) { return false; } Money money (Money) o; return Objects.equals(amount, money.amount); } Override public int hashCode() { return Objects.hash(amount); } }值对象的特征在这里体现得很明显不可变、通过工厂方法创建、在构造时完成业务规则校验。这样金额为负数的问题在模型层就被拦截了而不是等到数据库写入时才暴露。再定义订单聚合根Order// 文件路径order-domain/src/main/java/com/example/order/domain/model/Order.java package com.example.order.domain.model; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; /** * 订单聚合根。 * 负责维护订单项、金额合计和状态流转。 */ public class Order { private final String orderId; private final String customerId; private final ListOrderItem items; private OrderStatus status; private Money totalAmount; private Order(String orderId, String customerId) { this.orderId orderId; this.customerId customerId; this.items new ArrayList(); this.status OrderStatus.CREATED; this.totalAmount Money.zero(); } public static Order create(String customerId) { return new Order(UUID.randomUUID().toString(), customerId); } public void addItem(String productId, String productName, int quantity, BigDecimal price) { if (quantity 0) { throw new IllegalArgumentException(商品数量必须大于0); } if (status ! OrderStatus.CREATED) { throw new IllegalStateException(订单已提交不能再添加商品); } OrderItem item new OrderItem(productId, productName, quantity, Money.of(price)); items.add(item); recalculateTotalAmount(); } public void submit() { if (items.isEmpty()) { throw new IllegalStateException(订单不能为空); } if (status ! OrderStatus.CREATED) { throw new IllegalStateException(订单状态不允许提交); } this.status OrderStatus.SUBMITTED; } public void pay() { if (status ! OrderStatus.SUBMITTED) { throw new IllegalStateException(只有已提交的订单才能支付); } this.status OrderStatus.PAID; } public void cancel() { if (status OrderStatus.PAID) { throw new IllegalStateException(已支付订单不能直接取消); } this.status OrderStatus.CANCELED; } public void assertInvariants() { if (items.isEmpty()) { throw new IllegalStateException(订单必须包含至少一个订单项); } if (totalAmount.getAmount().compareTo(BigDecimal.ZERO) 0) { throw new IllegalStateException(订单金额必须大于0); } } private void recalculateTotalAmount() { Money sum Money.zero(); for (OrderItem item : items) { sum sum.add(item.getSubtotal()); } this.totalAmount sum; } public String getOrderId() { return orderId; } public String getCustomerId() { return customerId; } public OrderStatus getStatus() { return status; } public Money getTotalAmount() { return totalAmount; } public ListOrderItem getItems() { return Collections.unmodifiableList(items); } }代码里值得注意的点addItem、submit、pay、cancel这几个方法都先校验状态再改变状态。这就是把业务规则放进了模型而不是由调用方去判断。assertInvariants()是一个显式的不变量校验方法。它可以在任何关键节点被调用比如保存前、提交后用来自查模型内部的业务约束是否仍然成立。getItems()返回不可变列表防止外部直接修改订单项集合。再补充订单项和状态枚举// 文件路径order-domain/src/main/java/com/example/order/domain/model/OrderItem.java package com.example.order.domain.model; public class OrderItem { private final String productId; private final String productName; private final int quantity; private final Money unitPrice; private final Money subtotal; public OrderItem(String productId, String productName, int quantity, Money unitPrice) { this.productId productId; this.productName productName; this.quantity quantity; this.unitPrice unitPrice; this.subtotal unitPrice.multiply(quantity); } public String getProductId() { return productId; } public String getProductName() { return productName; } public int getQuantity() { return quantity; } public Money getUnitPrice() { return unitPrice; } public Money getSubtotal() { return subtotal; } }// 文件路径order-domain/src/main/java/com/example/order/domain/model/OrderStatus.java package com.example.order.domain.model; public enum OrderStatus { CREATED, SUBMITTED, PAID, CANCELED }6.2 把业务不变量写成可执行的模型校验有些业务规则不能只靠模型内部的方法约束尤其是涉及跨聚合校验的场景。比如订单提交时可能还要检查库存、检查用户信用额度。这些规则如果散落在应用服务里很难验证。更稳妥的方式是引入一个领域服务把规则集中组织。// 文件路径order-domain/src/main/java/com/example/order/domain/service/OrderDomainService.java package com.example.order.domain.service; import com.example.order.domain.model.Order; /** * 领域服务负责跨聚合的规则编排。 * 保持领域层内部使用不直接暴露给上层。 */ public class OrderDomainService { public void submitOrder(Order order) { order.assertInvariants(); // 这里可以补充跨聚合的校验逻辑 // 例如调用账号域的信用校验接口、调用库存域的库存占用接口。 // 注意这里的调用应该面向领域接口而不是直接依赖基础设施实现。 order.submit(); } }这里体现了一个关键设计领域服务依赖的是领域层的抽象接口而不是基础设施层的具体实现。这样跨聚合的规则也可以被单元测试覆盖不需要启动 Spring 容器。6.3 使用 ArchUnit 守护领域模型边界领域模型写好了能不能在后续迭代中保持不变这是“可验证”体系中最重要的一环。ArchUnit 是 Java 生态中非常成熟的架构约束测试库它能把架构规则写成 JUnit 测试随 CI 一起跑。添加依赖!-- 文件路径order-domain/pom.xml -- dependency groupIdcom.tngtech.archunit/groupId artifactIdarchunit-junit5/artifactId version1.2.1/version scopetest/scope /dependency注意ArchUnit 版本以实际引入的版本为准上面是常用版本之一。编写架构守护测试// 文件路径order-domain/src/test/java/com/example/order/ArchitectureRuleTest.java package com.example.order; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.lang.ArchRule; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; public class ArchitectureRuleTest { private final JavaClasses classes new ClassFileImporter() .importPackages(com.example.order); Test void domainLayerShouldNotDependOnInfrastructureLayer() { ArchRule rule noClasses() .that().resideInAPackage(..domain..) .should().dependOnClassesThat() .resideInAnyPackage(..infrastructure.., ..interfaces..) .because(领域层不允许依赖基础设施和接口层); rule.check(classes); } Test void domainModelShouldOnlyDependOnJdkAndItself() { ArchRule rule classes() .that().resideInAPackage(..domain.model..) .should().onlyDependOnClassesThat() .resideInAnyPackage(..domain.model.., java.., javax..) .because(领域模型应该保持纯净不依赖任何外部框架); rule.check(classes); } Test void applicationServiceShouldNotBeDirectlyAccessedByInfrastructure() { ArchRule rule noClasses() .that().resideInAPackage(..infrastructure..) .should().dependOnClassesThat() .resideInAPackage(..application.service..) .because(基础设施层不能反向依赖应用服务层); rule.check(classes); } }这套守护规则解决了一个非常重要的实际问题当团队规模变大、人员流动加快时架构约束不会因为某个开发者不熟悉 DDD 而被破坏。代码提交到 CI 后如果违反了依赖规则构建会直接失败。6.4 领域模型行为测试模型规则的显式化最终要落到测试上。领域模型测试的核心特征是不需要启动 Spring 容器直接 new 对象调用方法断言结果。// 文件路径order-domain/src/test/java/com/example/order/domain/model/OrderTest.java package com.example.order.domain.model; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class OrderTest { Test void shouldCalculateTotalAmountWhenAddingItems() { Order order Order.create(customer-001); order.addItem(product-001, 商品A, 2, new BigDecimal(10.00)); order.addItem(product-002, 商品B, 1, new BigDecimal(20.00)); assertEquals(new BigDecimal(40.00), order.getTotalAmount().getAmount()); } Test void shouldThrowExceptionWhenAddingItemAfterSubmit() { Order order Order.create(customer-001); order.addItem(product-001, 商品A, 1, new BigDecimal(10.00)); order.submit(); assertThrows(IllegalStateException.class, () - order.addItem(product-002, 商品B, 1, new BigDecimal(20.00))); } Test void shouldAllowPayOnlyAfterSubmit() { Order order Order.create(customer-001); order.addItem(product-001, 商品A, 1, new BigDecimal(10.00)); assertThrows(IllegalStateException.class, order::pay); order.submit(); order.pay(); assertEquals(OrderStatus.PAID, order.getStatus()); } Test void shouldRejectNegativeMoney() { assertThrows(IllegalArgumentException.class, () - Money.of(new BigDecimal(-1.00))); } Test void shouldRejectEmptyOrderSubmission() { Order order Order.create(customer-001); assertThrows(IllegalStateException.class, order::submit); } }这些测试覆盖的正是领域模型中最重要的业务不变量金额计算、状态流转、非法操作拦截。它们运行速度极快可以在每次本地构建时执行给开发者即时反馈。7. 运行与效果验证7.1 运行测试在order-domain目录下执行mvn test预期输出中会包含类似这样的结果[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] Results: [INFO] [INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] BUILD SUCCESS这里的 8 个测试包含 5 个领域模型行为测试和 3 个架构守护测试。全部通过说明模型行为正确、架构边界完整。7.2 故意破坏规则验证守护效果为了验证 ArchUnit 确实有效可以故意在领域模型的某个类中引入一个OrderRepositoryImpl的引用// 故意制造违规领域模型直接依赖了基础设施实现 import com.example.order.infrastructure.persistence.OrderRepositoryImpl; public class Order { private OrderRepositoryImpl repositoryImpl; }重新执行mvn test预期输出[ERROR] java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule no classes that reside in a package ..domain.. should depend on classes that reside in any package [..infrastructure.., ..interfaces..] was violated: Method com.example.order.domain.model.Order.init() calls constructor com.example.order.infrastructure.persistence.OrderRepositoryImpl.init() in (Order.java:0)这个失败信息就是“可验证”价值最直观的体现。它说明在没有代码审查介入的情况下自动化工具替我们守住了领域模型的边界。7.3 模型巡检的可视化脚本模型数量增多后写一个小脚本扫描模型结构可以快速掌握模型健康状况。下面是一个基于 Python 的简单巡检脚本扫描 Java 类的VO/Entity特征统计模型数量并识别潜在坏味道# 文件路径scripts/domain_sniffer.py import os import re import sys from collections import Counter ROOT sys.argv[1] if len(sys.argv) 1 else . PKG_KEYWORDS [domain, model, aggregate, valueobject] def scan_files(root): java_files [] for dirpath, _, filenames in os.walk(root): for f in filenames: if f.endswith(.java): java_files.append(os.path.join(dirpath, f)) return java_files def classify(content): if re.search(r\benum\b, content): return ENUM if re.search(r\brecord\b, content): return VALUE_OBJECT if re.search(r\bclass\b.*\bEntity\b, content): return ENTITY if re.search(r\baggregate\b|\bAggregateRoot\b, content, re.IGNORECASE): return AGGREGATE_ROOT return OTHER def main(): files scan_files(ROOT) stats Counter() for f in files: normalized f.replace(os.sep, /) if not any(k in normalized.lower() for k in PKG_KEYWORDS): continue with open(f, r, encodingutf-8) as fh: content fh.read() cls classify(content) stats[cls] 1 print( 领域模型巡检报告 ) for k, v in stats.items(): print(f{k}: {v}) if __name__ __main__: main()运行巡检python3 scripts/domain_sniffer.py order-domain/src/main/java输出示例 领域模型巡检报告 AGGREGATE_ROOT: 1 VALUE_OBJECT: 1 ENUM: 1 ENTITY: 2这个脚本很简单但它说明了一个重要思路模型的健康状况可以通过代码结构进行机器检查而不是依赖某个人翻开源码逐行阅读。实际项目中可以把这个巡检脚本接入定时任务或 CI 的定时执行每次输出报告与上次对比观察模型变化趋势。8. 常见问题与排查思路在实际落地这套验证体系时常见问题主要集中在以下几个方面。问题现象可能原因排查方式解决方案ArchUnit 测试报错但代码看起来没问题包名匹配规则写得不精确导致误判检查 ArchUnit 的包名通配符是否正确匹配到所有子包使用..domain..这种双点通配符确保匹配任意层级子包领域模型测试运行慢测试间接加载了 Spring 上下文检查测试类是否带有SpringBootTest注解去掉SpringBootTest领域模型测试应当是纯单元测试新增需求导致模型改动大聚合边界划分不合理模型承担了过多的职责检查聚合内对象的关联关系评估是否跨了限界上下文重新审视限界上下文划分必要时拆分为多个聚合模型规则在代码评审时才被发现写错部分业务规则没有通过代码表达只存在于文档或口头沟通中抽查场景新需求上线后是否有任何测试失败将关键业务不变量写成显式方法或值对象校验并补充测试巡检脚本统计不准确类名和注解命名不统一脚本匹配规则覆盖不全查看被分类为 OTHER 的类评估是否存在漏判建立模型类命名规范比如聚合根统一使用XxxAggregate命名团队新人不熟悉 DDD容易写坏领域层缺少开发规范说明和自动化守护检查 CI 中是否执行了架构守护测试把 ArchUnit 测试加入 CI 必要检查项并补充开发规范文档这里的核心经验是先让机器替你检查再让人去 review。机器能覆盖的规则全部自动化人只关注机器覆盖不到的领域逻辑。9. 最佳实践与工程建议9.1 建模阶段就同步写验证规则很多团队的流程是“先建模后补测试”。这导致验证规则经常滞后模型已经写完了才想起来补测试漏掉了很多边界条件。更推荐的做法是建模的同时定义该模型的业务不变量清单然后把这些不变量直接翻译成测试方法。比如创建Order聚合时同时就要写出空订单提交必须报错已提交订单不能添加商品已支付订单不能取消订单金额不能为负数。每一条不变量对应一个测试方法。这样模型从创建第一天起就是可验证的。9.2 把架构守护测试接入 CI而不是只在本地跑ArchUnit 测试如果只在本地执行效果会大打折扣。因为总有开发者忘记跑测试也总有代码合并时覆盖掉其他人的改动。把这些测试放入 GitLab CI 或 GitHub Actions 的测试阶段作为 merge 的必过项才能真正发挥“守护”作用。9.3 用“新增模型”而不是“修改模型”来支撑扩展“可验证领域模型能力扩展无上限”这句话在实践中的真正含义是当业务规则发生变更时我们倾向于通过新增领域模型来扩展能力而不是修改已有模型的核心行为。这符合开闭原则也让已有模型的验证规则保持稳定大幅降低回归风险。虽然无法做到绝对“无上限”但通过这种方式模型的扩展能力会越来越接近“可预测、可控制”。9.4 保持领域层的纯净性避免框架污染领域层应该是整个系统中依赖最少的模块。推荐只依赖 JDK 和极少数公共工具库。不要直接引入 Spring 的Component、Service等注解也不要在领域对象中注入仓储实现。领域对象的生命周期应该完全由应用服务负责领域对象自身只负责业务规则。如果确实需要在领域层使用依赖注入可以定义领域接口将实现放在基础设施层通过构造函数注入或服务定位器来解耦。但最简单稳妥的做法就是领域层只写纯 Java 对象不写任何框架注解。9.5 定期巡检关注模型腐败信号结合 7.3 节的巡检脚本思路建议团队每两周或每个迭代跑一次模型巡检关注以下信号聚合根数量是否在快速增长如果是说明限界上下文可能划分过细是否存在大量只包含 getter/setter 的“贫血模型”如果是说明业务逻辑可能又回到了 Service 层值对象是否被替换成可变的实体对象如果是说明模型约束正在被削弱。这些信号是模型健康度的晴雨表。越早发现修正成本越低。10. 总结与下一步实践现在把核心观点再拉回来领域模型不是画出来的而是验证出来的。只有通过显式规则、自动化架构守护、行为测试和可视化巡检领域模型才能真正成为团队可以信赖的业务逻辑容器。在这个前提下模型的能力扩展才能做到又快又稳——因为每一次扩展都有验证兜底每一次改动都能被机器检查。从实际项目的改进路径来看建议按照以下顺序推进第一步梳理现有领域模型列出核心业务不变量清单第二步把不变量翻译成领域模型测试用测试固定当前行为第三步引入 ArchUnit 或类似工具建立架构守护测试第四步设置模型巡检脚本定期生成模型健康报告第五步将以上检查全部接入 CI让验证成为开发流程的一部分。这五步做完之后你可能会发现一个明显的改变以前不敢动的领域层代码现在有了测试和架构守护托底改起来不再心惊胆战以前需要反复口头确认的业务规则现在变成了可执行、可回归、可追溯的测试用例。建议收藏本文在下一个领域建模项目开工时按照文中的四支柱思路逐项落地。你的团队会少走很多弯路。