CQRS 和事件溯源在 Kotlin 中的实践:构建完美代码的架构

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么 CQRS 和事件溯源不仅仅是流行词汇

\n\n

想象一下,你的代码是一座巨大的博物馆。通常我们只保存展品的最后一个版本:“订单当前的样子”。但如果我们想知道订单如何变成这个状态呢?是谁按下了“发送”按钮?状态是什么时候改变的?正是在这里,CQRS(命令查询职责分离)和事件溯源在Kotlin中登场了。

\n\n

在本指南中,我们不会只是从维基百科复制理论。我们将分析真实的示例,编写代码,并理解这些模式如何将 CRUD 操作的混乱转变为清晰的架构。如果你厌倦了“意大利面条式代码”,并希望构建易于扩展和调试的系统——那么你来对地方了。

\n\n

用简单的话解释什么是 CQRS

\n\n

在标准架构中,我们使用相同的模型来读写数据。这就像你用同一把勺子喝汤并同时搅拌它。CQRS 建议分离职责:

\n\n
    \n
  • Command(命令)——改变状态。写入请求(创建、更新、删除)。
  • \n
  • Query(查询)——读取状态。仅限 SELECT 操作。
  • \n
\n\n

为什么需要这样做?想象一个在线商店。当用户下订单时,我们必须检查库存、扣减商品、在数据库中创建记录、发送电子邮件。这是一个复杂的命令。而当客户只是查看他们的订单列表时——这是一个简单的查询。将它们混合在同一个方法中——是通往 bug 的捷径。

\n\n

在 Kotlin 中使用 CQRS 的优势

\n
    \n
  • 性能:可以以不同方式优化读写(不同的数据库、缓存、索引)。
  • \n
  • 安全性:分离访问权限(管理员写入,用户读取)。
  • \n
  • 可扩展性:命令和查询可以分布到不同的服务器上。
  • \n
\n\n

事件溯源:保存历史,而非事实

\n\n

如果说 CQRS 分离了操作,那么事件溯源则改变了数据存储的方式。我们不是存储当前状态(例如,“余额:1000 卢布”),而是存储一系列事件:“存入 500”,“消费 200”,“扣除手续费 50”。

\n\n

当前状态只是按顺序应用所有事件的结果。这赋予了我们超能力:我们可以“回放”时间,恢复到任何日期的状态,或者分析用户行为。

\n\n

现实生活中的示例:银行账户

\n
// 事件\ndata class MoneyDeposited(val amount: BigDecimal, val timestamp: Instant)\ndata class MoneyWithdrawn(val amount: BigDecimal, val timestamp: Instant)\n\n// 聚合(实体)\nclass BankAccount {\n    private var balance: BigDecimal = BigDecimal.ZERO\n    private val events = mutableListOf<Any>()\n\n    fun deposit(amount: BigDecimal) {\n        if (amount <= BigDecimal.ZERO) throw IllegalArgumentException("金额必须为正数")\n        val event = MoneyDeposited(amount, Instant.now())\n        apply(event)\n        events.add(event)\n    }\n\n    fun withdraw(amount: BigDecimal) {\n        if (amount > balance) throw IllegalStateException("余额不足")\n        val event = MoneyWithdrawn(amount, Instant.now())\n        apply(event)\n        events.add(event)\n    }\n\n    private fun apply(event: Any) {\n        when (event) {\n            is MoneyDeposited -> balance += event.amount\n            is MoneyWithdrawn -> balance -= event.amount\n        }\n    }\n\n    fun getBalance(): BigDecimal = balance\n    fun getUncommittedEvents(): List<Any> = events.toList()\n}
\n\n

CQRS 和事件溯源如何协同工作

\n\n

这是经典的组合。CQRS 负责分离通道,而事件溯源 负责存储

博客

书籍推荐