You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

六边形架构中异常应在适配器还是服务层抛出?附代码求反馈

六边形架构下异常抛出位置与代码优化建议

异常该抛在核心层还是适配器层?

先明确六边形架构的核心逻辑:核心层(领域模型、应用服务)负责纯业务规则,适配器层(比如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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 07:23:10