受检异常不适用于业务规则校验,应仅用于强制处理外部不确定性;业务规则宜用结构化响应或自定义runtimeexception;其合理场景限于sdk接口、金融强契约及遗留系统桥接。

受检异常本身不适合直接用于业务规则校验——它不是为表达“用户名已存在”“库存不足”这类可预期的业务拒绝而设计的。它的核心价值在于强制调用方面对**外部不确定性**(如文件读取失败、网络超时、数据库连接中断)做出显式决策。把业务规则违规包装成受检异常,反而会污染领域模型、增加调用负担、掩盖真实错误边界。
业务规则应优先返回结构化结果或抛运行时异常
对“订单金额不能为负”“收货地址不能为空”等校验,推荐以下方式:
- 统一使用 ApiResponse
或 Result 封装成功/失败,失败时携带语义化错误码与字段信息(如 {"code":"INVALID_AMOUNT","field":"amount","message":"金额必须大于零"}) - 若需抛异常,应使用继承自 RuntimeException 的自定义业务异常(如 InvalidOrderException),不强制上层捕获,便于单元测试和流程分支识别
- Controller 层统一拦截此类异常,转为标准 HTTP 响应(如 400 Bad Request),避免堆栈泄露
受检异常的合理使用场景很有限
仅在以下明确需要编译期强约束的环节谨慎引入:
- 对外 SDK 接口:例如支付回调确认方法声明 throws PaymentConfirmTimeoutException,迫使集成方必须处理超时重试逻辑
- 金融强契约流程:如跨境汇款发起后,必须等待监管报文回执,失败需人工介入,此时用受检异常防止静默忽略
- 遗留系统桥接层:老协议规定某类错误必须以特定错误码返回,且调用方无法升级框架,可封装为受检异常并附带清晰 Javadoc
真正起效的是契约驱动的校验机制
比异常类型更重要的是校验发生的时机与表达方式:
- 在命令入口(如 CreateWaybillCommand 处理器)做集中校验:客户额度、路由规则、必填字段完整性 —— 此处可用受检异常作为“流程守门人”,但仅限应用层
- 领域层聚合根内只抛非受检领域异常(如 InvalidWaybillException),专注业务不变量,不依赖外部设施
- 工作流引擎中用 Schema 定义节点输入契约,调用 validate() 方法替代 throws 声明,失败时返回结构化错误列表,而非抛异常
基础设施异常必须封装转化
底层技术异常(IOException、SQLException)绝不能穿透到业务层:
- 数据访问层捕获原始异常,转换为语义清晰的运行时异常(如 DataAccessException、PersistenceException)
- 避免在 service 方法签名中出现 throws IOException —— 这等于把文件系统细节暴露给业务逻辑
- Spring 等框架默认做法就是如此,保持领域层纯粹性与可测试性











