1. 概述

本文将带你深入理解如何在 Kotlin 项目中应用整洁架构(Clean Architecture)。其核心思想可以用一句话概括:依赖关系只能由外向内,绝不允许反向依赖

这意味着:

✅ 内层模块(如业务逻辑)不能直接引用外层实现(如数据库、框架)
❌ 外层可以调用内层,但内层绝不能感知外层的存在

这种设计让核心业务逻辑完全独立于外部框架、数据库甚至 UI 技术栈——换言之,你的 Use Case 不应该知道你是用 Spring 还是 Ktor,也不该关心数据存在 MySQL 还是 MongoDB。

💡 踩坑提醒:很多人一开始会把 Repository 接口放在 infrastructure 包里,这是典型的层级倒置!接口属于内层,实现才在外层。

2. 整洁架构的优势

采用 Clean Architecture 带来的收益非常明确,尤其适合中大型项目:

  • 可维护性强:修改某一层不会波及整个系统,避免“改一处,崩一片”
  • 业务逻辑隔离清晰:Use Case 和 Entity 完全脱离框架,新增功能或重构时更有安全感
  • 测试友好:各层高度解耦,单元测试和集成测试可以分层独立进行
  • 技术栈灵活替换:比如从 Ktor 切到 Spring Boot,只需重写适配器,核心逻辑不动

对于长期迭代的项目来说,前期多花点结构设计成本,后期省下的 debug 时间远超投入。

3. 可能遇到的问题

当然,没有银弹。Clean Architecture 也有它的代价:

  • ⚠️ 过度设计风险:小项目或 PoC 快速验证场景下,分层反而增加复杂度
  • ⚠️ 学习与协作成本高:团队成员需统一理解分层规则,否则容易出现“伪整洁”代码
  • ⚠️ 类数量增多:每个 Use Case 一个类、每个接口一个实现,初期看着类很多

📌 建议:中小型项目可先做简化版四层架构(Entity → UseCase → Interface → Adapter),不必追求完全标准。

4. 分层结构详解

典型的 Clean Architecture 分为以下五层,由内到外依次为:

4.1 实体(Entities)

代表领域核心模型,是业务的本质抽象。它不依赖任何框架、数据库 ORM 注解或网络序列化库

在 Kotlin 中通常使用 data class 表示:

data class User(val id: Long, val username: String, val email: String)

🔍 注意:这个 User 类不应该有任何 @Entity@Serializable 等注解。那些是外层的事!

4.2 用例(Use Cases)

也叫“交互逻辑”或“应用服务”,封装具体的业务行为。它是连接外部请求与内部实体的桥梁。

关键原则:

  • 每个 Use Case 应职责单一
  • 通过构造函数注入依赖(通常是接口)
  • 不暴露实现细节

示例:创建用户的用例

class CreateUserUseCase(private val userRepository: UserRepository) {
    fun execute(username: String, email: String): User {
        return userRepository.createUser(username, email)
    }
}

💬 小技巧:方法名建议统一为 executeinvoke,保持一致性;也可根据语义命名如 register()

4.3 接口(Interfaces / Protocols)

这里说的“接口”不是 HTTP 接口,而是指内层定义、外层实现的抽象契约,比如 UserRepository

它的作用是:

  • 隔离核心逻辑与外部依赖
  • 支持多实现(内存存储 / DB / Mock)
  • 实现依赖反转(DIP)

定义方式如下:

interface UserRepository {
    fun createUser(username: String, email: String): User
}

📌 关键点:这个接口必须放在内层包中(如 domain.repository),而实现类在外层。

4.4 框架层(Frameworks)

这一层负责集成第三方技术栈,比如 Web 框架、数据库驱动等。常见的包括:

  • Ktor / Spring Boot 用于构建 HTTP 接口
  • Exposed / JPA 处理数据持久化
  • Kafka / RabbitMQ 客户端处理消息通信

例如,使用 Ktor 实现一个简单的用户管理路由:

routing {
    route("/users") {
        get {
            call.respond(users)
        }
        post {
            val newUser = call.receive<User>()
            users.add(newUser)
            call.respond(HttpStatusCode.Created, newUser)
        }
        get("/{username}") {
            val username = call.parameters["username"]
            val user = users.find { it.username == username }
            if (user != null) {
                call.respond(user)
            } else {
                throw NotFoundException("User not found")
            }
        }
    }
}

⚠️ 注意:这里的 users 是临时内存集合,仅作演示。真实项目应通过 Use Case 调用。

4.5 适配器(Adapters)

适配器是内外层之间的翻译官,分为两类:

输入适配器(Input Adapters)

处理来自外部世界的输入,比如:

  • HTTP 请求(Ktor Controller、Spring MVC)
  • CLI 命令行调用
  • 消息队列监听器

它们的任务是:

  1. 解析原始输入(JSON、参数)
  2. 校验数据合法性
  3. 转换为 Use Case 可接受的格式
  4. 触发对应 Use Case 执行

典型代表就是 Web 层的路由或 Controller。

输出适配器(Output Adapters)

负责与外部系统交互,常见类型有:

  • 数据库访问实现(JPA、MyBatis、Exposed)
  • 第三方 API 调用客户端
  • 文件系统读写
  • 缓存操作(Redis)

下面是一个输出适配器的例子——基于内存的 UserRepository 实现:

class DatabaseUserRepository : UserRepository {
    private val users = mutableMapOf<Long, User>()
    private var idCounter = 1L
    override fun createUser(username: String, email: String): User {
        val newUser = User(idCounter++, username, email)
        users[newUser.id] = newUser
        return newUser
    }
}

✅ 正确姿势:DatabaseUserRepository 实现了内层定义的 UserRepository 接口,实现了“外层实现内层契约”。

📌 结构示意:

           ┌─────────────┐
           │   Framework │ ← Ktor, Spring
           └─────────────┘
                   ↓
           ┌─────────────┐
           │   Adapter   │ ← DatabaseUserRepository
           └─────────────┘
                   ↓
           ┌─────────────┐
           │  Interface  │ ← UserRepository (interface)
           └─────────────┘
                   ↓
         ┌──────────────────┐
         │ Use Case / Entity│ ← CreateUserUseCase, User
         └──────────────────┘

5. 总结

本文系统梳理了 Kotlin 项目中实施 Clean Architecture 的关键要素:

  • 核心在于依赖方向不可逆
  • 分层不是越多越好,关键是职责清晰
  • 接口定义在内层,实现在外层
  • 通过适配器完成内外数据转换

虽然初期会觉得“绕”,但一旦形成习惯,你会发现代码的可读性、可测性和可扩展性显著提升。特别是在团队协作和长期维护场景下,整洁架构的价值尤为突出。

🎯 最后建议:可以从一个小模块开始试点,逐步推广到整个项目,避免一开始就全面铺开带来的阻力。


原始标题:Clean Architecture with Kotlin