领域模型应使用非受检异常表达业务规则违规,验证逻辑内聚于聚合根或值对象中,异常向上冒泡由统一处理器映射为http状态码、结构化响应与上下文日志,严禁在领域层捕获处理或用作流程分支。

领域模型中验证逻辑不该被if-else淹没,也不该和数据库操作混在一起。非受检异常(即继承 RuntimeException 的异常)天然具备向上冒泡能力,无需强制捕获,这恰好契合领域模型“专注业务规则、拒绝技术细节”的设计原则——它让验证失败能干净利落地穿透服务层、控制器层,最终由统一异常处理器收口,实现验证逻辑与流程控制的解耦。
验证逻辑只留在领域对象内部
把校验规则封装进聚合根或值对象的构造方法、行为方法中,一旦不满足业务约束,立即抛出定制的非受检异常。例如:
-
用户注册时校验邮箱格式与唯一性:在
User构造函数中检查邮箱正则,调用仓储接口查重;不通过就抛InvalidEmailException或DuplicateEmailException,不返回布尔值、不设标志位。 -
订单创建时校验库存与金额:在
Order.place()方法中触发Inventory.check()和PriceCalculator.validate();任一失败即抛对应异常,不靠返回码判断下一步。
禁止在领域层做 try-catch 吞异常
领域模型不是错误处理中心。它只负责“发现错”和“声明错”,不负责“消化错”。任何在聚合根、实体或值对象里捕获自身抛出的非受检异常并静默处理、转成日志或默认值的行为,都会污染模型语义、掩盖业务意图。
- ❌ 错误示范:
try { placeOrder() } catch (InsufficientStockException e) { this.status = "PENDING_APPROVAL"; } - ✅ 正确做法:让
InsufficientStockException直接冒出,交由应用层(如OrderService)决定是转人工审核、降级处理,还是直接拒绝。
用统一异常处理器接管冒泡终点
非受检异常冒泡的终点不应是 500 页面或空指针堆栈,而应是 Spring 的 @ControllerAdvice 或全局异常拦截器。这里完成三件事:
- 将领域异常映射为标准 HTTP 状态码(如
UserAlreadyExistsException → 409 Conflict); - 提取异常中的业务字段(如
email、skuId),组装结构化错误响应体; - 记录带领域上下文的日志(如 “用户注册失败:邮箱 test@x.com 已存在”,而非 “NullPointerException at UserService.java:42”)。
避免把异常当流程分支用
非受检异常适合表达“本不该发生、需人工干预”的业务违规,但不适合替代常规控制流。例如:
- ✅ 合理:支付时余额不足 →
InsufficientBalanceException(违反核心业务前提); - ❌ 过度:用户未登录就访问首页 → 不该抛
UnauthorizedAccessException,而应走认证过滤器返回401;这不是领域规则破坏,是横切关注点。
一句话总结:让异常回归本质——它是业务契约被打破的信号灯,不是if-else的替身。










