DDD洋葱架构下基础设施层到表现层的异常传递方案咨询
核心结论
你对「存储实现细节不应出现在Domain层」的认知完全正确。Domain层只负责核心业务规则的定义,所有和外部实现、技术细节相关的内容都不能侵入Domain层,否则会破坏架构的解耦性,后续切换存储方案时会产生大量上层冗余修改。
现有方案的问题
你之前考虑将FileExistsException作为Application层契约的方案不合理,本质是把基础设施的实现细节耦合到了上层,后续切换数据库存储时,Application层需要新增数据库相关异常的处理逻辑,违反开闭原则。
正确的异常处理方案
采用「业务语义异常抽象+基础设施层转换」的实现方式:
- 在上层(Domain层或Application层的公共契约模块)定义不带任何实现细节的业务异常类型,比如:
DuplicateResourceException:资源重复异常,对应文件已存在、数据库唯一键冲突等场景ResourceAccessDeniedException:资源无权限访问异常,对应文件无读写权限、数据库账号权限不足等场景ResourceCorruptedException:资源损坏异常,对应JSON文件格式错误、数据库表结构损坏等场景ResourceOperationException:通用资源操作失败异常,兜底所有未知存储操作错误
- Infrastructure层的持久化实现类,捕获底层实现抛出的技术异常,转换为上述上层定义的业务语义异常后再向上抛出。比如本地文件存储实现捕获
FileAlreadyExistsException后转换为DuplicateResourceException抛出,数据库存储实现捕获唯一键冲突的SQL异常后也转换为DuplicateResourceException抛出。 - Application层只需要处理这些业务语义异常,完全不需要感知底层存储类型,Presentation层拿到异常后结合当前用户操作上下文,生成对应的用户提示即可,比如同样是
DuplicateResourceException,如果当前上下文是本地文件保存操作就提示「文件已存在,是否覆盖?」,如果是数据库记录保存操作就提示「该记录已存在,是否更新?」。
跨层信息共享的通用规则
所有跨层传递的信息都需要满足依赖倒置原则,核心规则如下:
- 契约定义在上游:所有跨层共享的接口、异常、数据结构,都必须定义在依赖流向的上游层(依赖方向是Presentation→Application→Domain←Infrastructure),不能由下游的基础设施层定义上游需要依赖的内容
- 只传递业务抽象:所有跨层传递的信息必须是业务语义的抽象,不能包含任何底层实现细节,比如不能传递文件名、SQL语句、连接参数这类和具体实现绑定的内容
- 下游做适配转换:所有基础设施层的外部返回值、异常,都必须在基础设施层内部完成转换,适配成上游定义的契约类型后再向上传递,避免实现细节泄漏到上层。
切换存储方案的适配逻辑
后续你需要切换为数据库存储时,只需要新增对应的Infrastructure层实现,在该实现中把数据库相关的所有技术异常转换为之前定义的业务语义异常即可,Application层、Presentation层的异常处理逻辑不需要做任何修改,完全符合开闭原则。
内容的提问来源于stack exchange,提问作者CEH
相关产品推荐
相关产品推荐

