Kotlin: Kotlin项目开发详解
最后更新:2026-08-26
设计完成,现在是代码时刻——Charlie、Alice 和 Bob 协作实现 OrderProcessor 的四层代码:领域层(Order + 事件)、服务层(业务逻辑 + 协程)、持久层(Repository)、集成层(HTTP 客户端)。
1. 你将学到
- 领域层: / /
- 服务层: + 异步处理 + 事件流
- 持久层:Spring Data R2DBC + 协程 Repository
- 集成层:Ktor HTTP Client 调用外部服务
- Alice/Bob/Charlie 协作开发流程
2. 一个团队的真实故事
(1) 痛点:分工不清晰导致冲突
Alice 和 Bob 同时修改 ,Git 冲突 10 次。原因:没有按层分工,两人都在改同一个文件。
(2) 分层分工的解法
TEXT
📖 仅展示
Charlie: Domain layer (Order, OrderEvent, StateMachine)
Alice: Service layer (OrderService, business logic)
Bob: Repository + Integration layer (data access, HTTP clients)
分层架构 = 分层分工,每层独立开发,接口契约减少冲突。
3. 领域层
(1) 核心实体
KOTLIN
// Domain entities - pure Kotlin, no framework dependency
data class OrderItem(
val sku: String,
val quantity: Int,
val unitPrice: Double
) {
val subtotal: Double get() = quantity * unitPrice
init {
require(quantity > 0) { "Quantity must be positive" }
require(unitPrice >= 0) { "Price must be non-negative" }
}
}
data class Address(
val street: String,
val city: String,
val country: String
)
data class Order(
val id: String,
val customerId: String,
val items: List`<OrderItem>`,
val shippingAddress: Address?,
var status: OrderStatus,
var trackingCode: String?,
val createdAt: String,
var updatedAt: String?
) {
val total: Double get() = items.sumOf { it.subtotal }
init {
require(id.startsWith("ORD-")) { "Invalid order ID format" }
require(items.isNotEmpty()) { "Order must have at least one item" }
}
}
(2) 状态与事件
KOTLIN
sealed class OrderStatus {
data class Pending(val createdAt: String) : OrderStatus()
data class Confirmed(val confirmedAt: String) : OrderStatus()
data class Shipped(val trackingCode: String, val shippedAt: String) : OrderStatus()
data class Delivered(val deliveredAt: String) : OrderStatus()
data class Cancelled(val reason: String, val cancelledAt: String) : OrderStatus()
}
sealed class OrderEvent {
abstract val orderId: String
abstract val timestamp: String
data class Created(
override val orderId: String,
val customerId: String,
val total: Double,
override val timestamp: String
) : OrderEvent()
data class Confirmed(override val orderId: String, override val timestamp: String) : OrderEvent()
data class Shipped(override val orderId: String, val trackingCode: String, override val timestamp: String) : OrderEvent()
data class Delivered(override val orderId: String, override val timestamp: String) : OrderEvent()
data class Cancelled(override val orderId: String, val reason: String, override val timestamp: String) : OrderEvent()
}
(3) 状态机
KOTLIN
object OrderStateMachine {
fun transition(current: OrderStatus, event: OrderEvent): OrderStatus = when {
current is OrderStatus.Pending && event is OrderEvent.Confirmed ->
OrderStatus.Confirmed(event.timestamp)
current is OrderStatus.Confirmed && event is OrderEvent.Shipped ->
OrderStatus.Shipped(event.trackingCode, event.timestamp)
current is OrderStatus.Shipped && event is OrderEvent.Delivered ->
OrderStatus.Delivered(event.timestamp)
current is OrderStatus.Pending && event is OrderEvent.Cancelled ->
OrderStatus.Cancelled(event.reason, event.timestamp)
else ->
throw IllegalStateException("Invalid transition from $current with $event")
}
fun canTransition(current: OrderStatus, event: OrderEvent): Boolean = when {
current is OrderStatus.Pending && event is OrderEvent.Confirmed -> true
current is OrderStatus.Confirmed && event is OrderEvent.Shipped -> true
current is OrderStatus.Shipped && event is OrderEvent.Delivered -> true
current is OrderStatus.Pending && event is OrderEvent.Cancelled -> true
else -> false
}
}
4. 服务层
(1) OrderService
KOTLIN
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class OrderService(
private val repository: OrderRepository,
private val eventStore: EventRepository,
private val paymentClient: PaymentClient,
private val inventoryClient: InventoryClient
) {
suspend fun createOrder(request: CreateOrderRequest): Order {
val order = Order(
id = generateOrderId(),
customerId = request.customerId,
items = request.items.map { OrderItem(it.sku, it.quantity, it.unitPrice) },
shippingAddress = request.shippingAddress,
status = OrderStatus.Pending(request.timestamp),
trackingCode = null,
createdAt = request.timestamp,
updatedAt = null
)
repository.save(order)
eventStore.append(OrderEvent.Created(order.id, order.customerId, order.total, request.timestamp))
return order
}
suspend fun confirmOrder(orderId: String, timestamp: String): Order {
val order = repository.findById(orderId) ?: throw NoSuchElementException("Order $orderId not found")
val event = OrderEvent.Confirmed(orderId, timestamp)
val newStatus = OrderStateMachine.transition(order.status, event)
order.status = newStatus
order.updatedAt = timestamp
repository.save(order)
eventStore.append(event)
return order
}
suspend fun shipOrder(orderId: String, trackingCode: String, timestamp: String): Order {
val order = repository.findById(orderId) ?: throw NoSuchElementException("Order $orderId not found")
val event = OrderEvent.Shipped(orderId, trackingCode, timestamp)
val newStatus = OrderStateMachine.transition(order.status, event)
order.status = newStatus
order.trackingCode = trackingCode
order.updatedAt = timestamp
repository.save(order)
eventStore.append(event)
return order
}
fun orderEvents(orderId: String): Flow`<OrderEvent>` = flow {
eventStore.findByOrderId(orderId).forEach { emit(it) }
}
private fun generateOrderId(): String = "ORD-${System.currentTimeMillis()}"
}
(2) 请求/响应 DTO
KOTLIN
data class CreateOrderRequest(
val customerId: String,
val items: List`<CreateItemRequest>`,
val shippingAddress: AddressRequest?,
val timestamp: String
)
data class CreateItemRequest(val sku: String, val quantity: Int, val unitPrice: Double)
data class AddressRequest(val street: String, val city: String, val country: String)
data class OrderResponse(
val id: String,
val customerId: String,
val total: Double,
val status: String,
val items: List`<OrderItemResponse>`,
val trackingCode: String?,
val createdAt: String,
val updatedAt: String?
)
data class OrderItemResponse(val sku: String, val quantity: Int, val unitPrice: Double, val subtotal: Double)
fun Order.toResponse() = OrderResponse(
id, customerId, total, status::class.simpleName ?: "Unknown",
items.map { OrderItemResponse(it.sku, it.quantity, it.unitPrice, it.subtotal) },
trackingCode, createdAt, updatedAt
)
5. 持久层
(1) Repository 接口
KOTLIN
interface OrderRepository {
suspend fun save(order: Order)
suspend fun findById(id: String): Order?
suspend fun findAll(): List`<Order>`
suspend fun deleteById(id: String)
}
interface EventRepository {
suspend fun append(event: OrderEvent)
fun findByOrderId(orderId: String): List`<OrderEvent>`
}
(2) 内存实现(开发阶段)
KOTLIN
class InMemoryOrderRepository : OrderRepository {
private val storage = mutableMapOf<String, Order>()
override suspend fun save(order: Order) { storage[order.id] = order }
override suspend fun findById(id: String) = storage[id]
override suspend fun findAll() = storage.values.toList()
override suspend fun deleteById(id: String) { storage.remove(id) }
}
class InMemoryEventRepository : EventRepository {
private val events = mutableListOf`<OrderEvent>`()
override suspend fun append(event: OrderEvent) { events.add(event) }
override fun findByOrderId(orderId: String) = events.filter { it.orderId == orderId }
}
6. 集成层
(1) HTTP 客户端接口
KOTLIN
interface PaymentClient {
suspend fun processPayment(orderId: String, amount: Double): PaymentResult
}
interface InventoryClient {
suspend fun checkAvailability(sku: String, quantity: Int): Boolean
suspend fun reserve(sku: String, quantity: Int): ReservationResult
}
sealed class PaymentResult {
data class Success(val transactionId: String) : PaymentResult()
data class Failed(val reason: String) : PaymentResult()
}
sealed class ReservationResult {
data class Reserved(val reservationId: String) : ReservationResult()
data class Unavailable(val reason: String) : ReservationResult()
}
// Simulated implementations
class MockPaymentClient : PaymentClient {
override suspend fun processPayment(orderId: String, amount: Double) =
PaymentResult.Success("TXN-${orderId.substring(4)}")
}
class MockInventoryClient : InventoryClient {
override suspend fun checkAvailability(sku: String, quantity: Int) = quantity < 10_000
override suspend fun reserve(sku: String, quantity: Int) =
ReservationResult.Reserved("RES-${sku.substring(4)}")
}
7. 四层职责对比
| 层 | 职责 | 依赖方向 | 典型类 | 测试策略 |
|---|---|---|---|---|
| 领域层 | 业务规则与实体 | 无依赖 | Order, OrderStatus, StateMachine | 纯单元测试 |
| 服务层 | 业务流程编排 | → 领域/持久/集成 | OrderService | Mock 依赖单元测试 |
| 持久层 | 数据存取 | → 领域 | OrderRepository, EventRepository | 集成测试(Testcontainers) |
| 集成层 | 外部服务调用 | → 领域 | PaymentClient, InventoryClient | 契约测试 + Mock |
(1) Repository 实现策略对比
| 阶段 | 实现 | 优点 | 缺点 |
|---|---|---|---|
| 开发 | InMemory* | 零配置、快速 | 无持久化 |
| 测试 | Testcontainers | 真实 DB、可复现 | 需要 Docker |
| 生产 | Spring Data R2DBC | 协程原生、高性能 | 需配置连接池 |
(2) 状态转换规则表
| 当前状态 | 允许事件 | 目标状态 |
|---|---|---|
| Pending | Confirmed | Confirmed |
| Pending | Cancelled | Cancelled |
| Confirmed | Shipped | Shipped |
| Shipped | Delivered | Delivered |
| 其他组合 | 非法 |
8. 订单创建完整时序
sequenceDiagram
participant Client
participant Controller
participant Service
participant Repo
participant Events
participant Payment
participant Inventory
Client->>Controller: POST /api/v1/orders
Controller->>Service: createOrder(request)
Service->>Inventory: checkAvailability(sku, qty)
Inventory-->>Service: Available
Service->>Repo: save(order)
Repo-->>Service: Saved
Service->>Events: append(Created event)
Service->>Payment: processPayment(orderId, total)
Payment-->>Service: Success
Service->>Repo: save(order status=Confirmed)
Service->>Events: append(Confirmed event)
Service-->>Controller: Order
Controller-->>Client: 201 Created
9. 完整示例
KOTLIN
// ============================================
// OrderProcessor - End-to-End Implementation
// Feature: Full order lifecycle with state machine
// ============================================
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
// --- Domain (from sections above) ---
data class OrderItem(val sku: String, val quantity: Int, val unitPrice: Double) {
val subtotal: Double get() = quantity * unitPrice
}
sealed class OrderStatus {
data class Pending(val at: String) : OrderStatus()
data class Confirmed(val at: String) : OrderStatus()
data class Shipped(val trackingCode: String, val at: String) : OrderStatus()
data class Cancelled(val reason: String, val at: String) : OrderStatus()
}
sealed class OrderEvent {
abstract val orderId: String
data class Created(override val orderId: String, val total: Double, val at: String) : OrderEvent()
data class Confirmed(override val orderId: String, val at: String) : OrderEvent()
data class Shipped(override val orderId: String, val code: String, val at: String) : OrderEvent()
data class Cancelled(override val orderId: String, val reason: String, val at: String) : OrderEvent()
}
data class Order(
val id: String, val customerId: String, val items: List`<OrderItem>`,
var status: OrderStatus, var trackingCode: String?, val createdAt: String, var updatedAt: String?
) { val total: Double get() = items.sumOf { it.subtotal } }
object StateMachine {
fun next(current: OrderStatus, event: OrderEvent): OrderStatus = when {
current is OrderStatus.Pending && event is OrderEvent.Confirmed -> OrderStatus.Confirmed(event.at)
current is OrderStatus.Confirmed && event is OrderEvent.Shipped -> OrderStatus.Shipped(event.code, event.at)
current is OrderStatus.Pending && event is OrderEvent.Cancelled -> OrderStatus.Cancelled(event.reason, event.at)
else -> error("Invalid: $current + $event")
}
}
// --- Repository ---
class OrderRepo {
private val db = mutableMapOf<String, Order>()
fun save(o: Order) { db[o.id] = o }
fun find(id: String) = db[id]
fun findAll() = db.values.toList()
}
class EventRepo {
private val events = mutableListOf`<OrderEvent>`()
fun append(e: OrderEvent) { events.add(e) }
fun find(orderId: String) = events.filter { it.orderId == orderId }
}
// --- Service ---
class OrderService(private val orders: OrderRepo, private val events: EventRepo) {
private var counter = 0
fun create(customerId: String, items: List`<OrderItem>`, at: String): Order {
val order = Order("ORD-${++counter}", customerId, items, OrderStatus.Pending(at), null, at, null)
orders.save(order)
events.append(OrderEvent.Created(order.id, order.total, at))
return order
}
fun confirm(id: String, at: String): Order {
val order = orders.find(id) ?: error("Not found: $id")
val event = OrderEvent.Confirmed(id, at)
order.status = StateMachine.next(order.status, event)
order.updatedAt = at
orders.save(order)
events.append(event)
return order
}
fun ship(id: String, code: String, at: String): Order {
val order = orders.find(id) ?: error("Not found: $id")
val event = OrderEvent.Shipped(id, code, at)
order.status = StateMachine.next(order.status, event)
order.trackingCode = code
order.updatedAt = at
orders.save(order)
events.append(event)
return order
}
fun cancel(id: String, reason: String, at: String): Order {
val order = orders.find(id) ?: error("Not found: $id")
val event = OrderEvent.Cancelled(id, reason, at)
order.status = StateMachine.next(order.status, event)
order.updatedAt = at
orders.save(order)
events.append(event)
return order
}
fun history(id: String) = events.find(id)
}
// --- Demo ---
fun main() {
val service = OrderService(OrderRepo(), EventRepo())
println("=== OrderProcessor Development Demo ===\n")
// Create orders
val o1 = service.create("CUST-001", listOf(OrderItem("SKU-A", 3, 9.99), OrderItem("SKU-B", 1, 149.99)), "2026-07-13T10:00:00Z")
println("Created: ${o1.id} | Total: \$${o1.total} USD | Status: ${o1.status}")
val o2 = service.create("CUST-002", listOf(OrderItem("SKU-C", 2, 5_000.0)), "2026-07-13T10:01:00Z")
println("Created: ${o2.id} | Total: \$${o2.total} USD | Status: ${o2.status}")
// Confirm
val confirmed = service.confirm(o1.id, "2026-07-13T10:05:00Z")
println("\nConfirmed: ${confirmed.id} | Status: ${confirmed.status}")
// Ship
val shipped = service.ship(o1.id, "TRK-ABC123", "2026-07-13T11:00:00Z")
println("Shipped: ${shipped.id} | Tracking: ${shipped.trackingCode} | Status: ${shipped.status}")
// Cancel
val cancelled = service.cancel(o2.id, "Customer request", "2026-07-13T10:10:00Z")
println("Cancelled: ${cancelled.id} | Reason: ${(cancelled.status as OrderStatus.Cancelled).reason}")
// Event history
println("\n=== Event History: ${o1.id} ===")
service.history(o1.id).forEach { println(" $it") }
}
输出:
TEXT
📖 仅展示
=== OrderProcessor Development Demo ===
Created: ORD-1 | Total: $179.96 USD | Status: Pending(at=2026-07-13T10:00:00Z)
Created: ORD-2 | Total: $10000.0 USD | Status: Pending(at=2026-07-13T10:01:00Z)
Confirmed: ORD-1 | Status: Confirmed(at=2026-07-13T10:05:00Z)
Shipped: ORD-1 | Tracking: TRK-ABC123 | Status: Shipped(trackingCode=TRK-ABC123, at=2026-07-13T11:00:00Z)
Cancelled: ORD-2 | Reason: Customer request
=== Event History: ORD-1 ===
Created(orderId=ORD-1, total=179.96, at=2026-07-13T10:00:00Z)
Confirmed(orderId=ORD-1, at=2026-07-13T10:05:00Z)
Shipped(orderId=ORD-1, code=TRK-ABC123, at=2026-07-13T11:00:00Z)
❓ 常见问题
Q 领域层应该依赖框架吗?
A 不应该。领域层是纯 Kotlin 代码,不依赖 Spring 或任何框架。这确保领域逻辑可独立测试和复用。
Q Service 层应该返回领域对象还是 DTO?
A Service 返回领域对象,Controller 负责转换为 DTO。分层职责清晰:Service 不知道 HTTP 的存在。
Q 状态机应该放在哪里?
A 放在领域层。状态机是核心业务规则,不应该依赖任何外部层。它用纯 Kotlin 密封类实现。
Q 如何处理并发状态更新?
A 数据库乐观锁(version 字段)或分布式锁。协程层面用 保护共享状态。生产环境推荐数据库锁。
Q 集成层的 HTTP 客户端怎么选?
A Kotlin 项目推荐 Ktor Client(协程原生),Java 生态可用 Spring WebClient。两者都支持 suspend。
Q 如何确保团队按层分工?
A 代码审查规则:Controller 不直接操作 Repository,Service 不知道 HTTP,领域层不依赖框架。ArchUnit 等工具可自动检查。
📖 小节
- 领域层:纯 Kotlin,data class + sealed class + 状态机,无框架依赖
- 服务层:业务逻辑 + 状态转换 + 事件发布,调用 Repository 和集成层
- 持久层:接口抽象 + 内存实现(开发)+ 数据库实现(生产)
- 集成层:HTTP 客户端接口 + Mock 实现(开发)+ Ktor/Spring 实现(生产)
- 分层分工:Charlie 领域、Alice 服务、Bob 持久/集成
- 状态机 + 事件溯源:每次状态变更记录事件,支持完整审计链
📝 作业
- 基础题(难度⭐):实现 data class 和 密封类,在 块中添加验证。提示:
- 进阶题(难度⭐⭐):实现 的 和 ,使用状态机验证转换合法性。提示:
- 挑战题(难度⭐⭐⭐):实现完整的四层 OrderProcessor:领域(Order + Event + StateMachine)、服务(OrderService + 协程)、持久(InMemoryRepo)、集成(MockPaymentClient)。提示:参考第 8 节完整示例