android-clean-architecture

面向 Android 与 Kotlin Multiplatform 的 Clean Architecture 实现指南。涵盖模块分层结构、依赖方向规则、UseCase / Repository / DataSource 三层数据模式,以及 Room / SQLDelight / Ktor 的数据层选型。同时提供 Koin 和 Hilt 两种依赖注入方案与结构化错误处理。

核心能力

Android & KMP 项目的 Clean Architecture 完整实践

为 Android 和 Kotlin Multiplatform 项目提供模块化的分层架构方案,明确 domain、data、presentation 等各层职责与依赖边界。通过 UseCase 封装业务操作、Repository 协调本地与远程数据源、DataSource 隔离具体技术实现,实现可测试、可替换、可演进的项目结构。

适用场景

  • 搭建 Android 或 KMP 项目的模块结构
  • 实现 UseCase、Repository、DataSource 三层数据流
  • 设计 domain / data / presentation 之间的数据流转
  • 配置 Koin 或 Hilt 依赖注入
  • 集成 Room(Android)、SQLDelight(KMP)、Ktor(网络层)等技术栈

推荐模块结构

project/
├── app/            # Android 入口,DI 装配,Application 类
├── core/           # 共享工具、基类、错误类型
├── domain/         # UseCase、领域模型、Repository 接口(纯 Kotlin)
├── data/           # Repository 实现、DataSource、数据库、网络
├── presentation/   # 页面、ViewModel、UI 模型、导航
├── design-system/  # 可复用 Compose 组件、主题、排版
└── feature/        # 特性模块(可选,大型项目使用)
    ├── auth/
    ├── settings/
    └── profile/

依赖规则(关键约束)

app → presentation, domain, data, core
presentation → domain, design-system, core
data → domain, core
domain → core(或零依赖)
core → (无依赖)

铁律domain绝对不可依赖 datapresentation 或任何框架,必须为纯 Kotlin。

Domain 层设计

UseCase 模式

每个 UseCase 代表一个业务操作,使用 operator fun invoke 实现简洁的调用语法:

class GetItemsByCategoryUseCase(
  private val repository: ItemRepository
) {
  suspend operator fun invoke(category: String): Result<List<Item>> {
    return repository.getItemsByCategory(category)
  }
}

也支持基于 Flow 的响应式 UseCase,用于实时数据流。

领域模型

纯 Kotlin data class,无任何框架注解:

data class Item(
  val id: String,
  val title: String,
  val description: String,
  val tags: List<String>,
  val status: Status,
  val category: String
)

enum class Status { DRAFT, ACTIVE, ARCHIVED }

Repository 接口

接口定义在 domain 层,具体实现放在 data 层:

interface ItemRepository {
  suspend fun getItemsByCategory(category: String): Result<List<Item>>
  suspend fun saveItem(item: Item): Result<Unit>
  fun observeItems(): Flow<List<Item>>
}

Data 层实现

Repository 实现

协调本地与远程数据源,封装数据同步逻辑:

class ItemRepositoryImpl(
  private val localDataSource: ItemLocalDataSource,
  private val remoteDataSource: ItemRemoteDataSource
) : ItemRepository {

  override suspend fun getItemsByCategory(category: String): Result<List<Item>> {
    return runCatching {
      val remote = remoteDataSource.fetchItems(category)
      localDataSource.insertItems(remote.map { it.toEntity() })
      localDataSource.getItemsByCategory(category).map { it.toDomain() }
    }
  }
}

Mapper 模式

将映射逻辑以扩展函数形式放在数据模型附近:

fun ItemEntity.toDomain() = Item(...)
fun ItemDto.toEntity() = ItemEntity(...)

技术选型

技术 平台 用途
Room Android 本地 SQLite 数据库,Entity + Dao
SQLDelight KMP 跨平台类型安全 SQL,生成 Kotlin API
Ktor KMP HTTP 客户端,支持内容协商、日志、默认请求配置

依赖注入

Koin(KMP 兼容)

跨平台首选,模块定义简洁:

val domainModule = module {
  factory { GetItemsByCategoryUseCase(get()) }
}
val dataModule = module {
  single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
}

Hilt(仅限 Android)

基于注解,适合纯 Android 项目:

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
  @Binds
  abstract fun bindItemRepository(impl: ItemRepositoryImpl): ItemRepository
}

错误处理

Result / Try 模式

使用 Result<T> 或自定义密封类型进行错误传播:

sealed interface Try<out T> {
  data class Success<T>(val value: T) : Try<T>
  data class Failure(val error: AppError) : Try<Nothing>
}

sealed interface AppError {
  data class Network(val message: String) : AppError
  data class Database(val message: String) : AppError
  data object Unauthorized : AppError
}

在 ViewModel 中将结果映射为 UI 状态:

viewModelScope.launch {
  when (val result = getItems(category)) {
    is Try.Success -> _state.update { it.copy(items = result.value, isLoading = false) }
    is Try.Failure -> _state.update { it.copy(error = result.error.toMessage(), isLoading = false) }
  }
}

Gradle Convention Plugins

KMP 项目使用 Convention Plugin 减少构建脚本重复:

// build-logic/src/main/kotlin/kmp-library.gradle.kts
plugins {
  id("org.jetbrains.kotlin.multiplatform")
}

kotlin {
  androidTarget()
  iosX64(); iosArm64(); iosSimulatorArm64()
}

模块直接应用:

// domain/build.gradle.kts
plugins { id("kmp-library") }

反模式(必须避免)

  • domain 层引入 Android 框架类 — 必须保持纯 Kotlin
  • 将数据库实体或 DTO 暴露给 UI 层 — 始终映射为领域模型
  • 在 ViewModel 中编写业务逻辑 — 提取到 UseCase
  • 使用 GlobalScope 或无结构协程 — 使用 viewModelScope 或结构化并发
  • 臃肿的 Repository — 拆分为聚焦的 DataSource
  • 循环模块依赖 — A 依赖 B,则 B 绝不可依赖 A
v1.0.0 2026-07-16
下载