六边形架构中异常应在适配器还是服务层抛出?附代码求反馈
六边形架构下异常抛出位置与代码优化建议
异常该抛在核心层还是适配器层?
先明确六边形架构的核心逻辑:核心层(领域模型、应用服务)负责纯业务规则,适配器层(比如JPA数据适配器、REST控制器)负责对接外部系统(数据库、前端)。
对于EntityNotFoundException这类异常,核心层(服务/领域层)抛出才是合理选择:
- 这个异常本质是业务逻辑层面的"操作前提不满足"——要操作的实体不存在,这属于业务规则范畴,核心层必须掌握这类规则的校验权,确保业务逻辑的完整性。
- 如果把抛出逻辑放在适配器层(比如JPA Repository里),等于把业务规则泄露到了外部依赖层,违背了六边形架构"核心独立于外部实现"的原则,后续换数据存储方式时,还要重新处理这部分逻辑,维护成本会很高。
适配器层的职责是处理技术类异常(比如数据库连接失败、HTTP请求格式错误),或者把核心层抛出的业务异常转换成外部系统能理解的形式(比如把自定义业务异常转成HTTP 404响应)。
针对Kotlin+Spring+JPA代码的优化反馈
假设你现有代码大概是这样:
@Service class UserService(private val userRepo: JpaUserRepository) { fun getUserById(id: Long): User { return userRepo.findById(id) .orElseThrow { EntityNotFoundException("User $id not found") } } } interface JpaUserRepository : JpaRepository<User, Long>
这里有几个可以贴合六边形架构的优化点:
1. 用自定义领域异常替代通用异常
别直接用Spring的EntityNotFoundException,自定义一个业务语义更明确的异常,比如UserNotFoundException,核心层只抛领域相关的异常,让业务逻辑更清晰:
// 核心层自定义领域异常 class UserNotFoundException(userId: Long) : RuntimeException("用户ID $userId 不存在")
然后服务层抛出这个异常,在REST适配器(控制器)里统一处理:
@RestController class UserController(private val userService: UserService) { @GetMapping("/users/{id}") fun getUser(@PathVariable id: Long): ResponseEntity<User> { return try { ResponseEntity.ok(userService.getUserById(id)) } catch (e: UserNotFoundException) { ResponseEntity.notFound().build() } } }
2. 核心层依赖端口,而非具体适配器
六边形架构要求核心层依赖抽象(端口),而非具体的外部实现。你需要把JpaUserRepository包装成核心层定义的端口接口:
// 核心层定义的用户数据访问端口 interface UserPort { fun findById(id: Long): User? } // JPA适配器实现这个端口 @Repository class JpaUserAdapter(private val jpaRepo: JpaUserRepository) : UserPort { override fun findById(id: Long): User? { return jpaRepo.findById(id).orElse(null) } }
然后服务层依赖UserPort,而不是直接依赖JpaUserRepository:
@Service class UserService(private val userPort: UserPort) { fun getUserById(id: Long): User { return userPort.findById(id) ?: throw UserNotFoundException(id) } }
这样核心层完全和Spring JPA解耦,后续哪怕换成MongoDB或者内存存储,只要实现UserPort接口就行,核心业务逻辑不用动。
3. 适配器层只做数据交互,别碰业务判断
JPA适配器的职责就是把数据库操作转换成核心层的模型,不要在里面加orElseThrow这类业务判断逻辑,把实体存在性校验完全交给核心服务层,保证业务逻辑的内聚性。
最后总结下:
- 业务类异常(比如找不到实体)扔在核心服务/领域层,确保业务规则不泄露。
- 适配器层负责处理技术异常,或者把业务异常转成外部系统能识别的响应。
- 自定义领域异常、依赖端口而非具体实现,能让你的代码更贴合六边形架构的设计,扩展性和可维护性都会更好。
内容的提问来源于stack exchange,提问作者gim minjae
相关产品推荐
相关产品推荐

