当常规CRUD失效时:在C++中认识CQRS和事件溯源
您是否遇到过这样的情况:您的C++程序在测试数据上运行良好,但在生产环境中却开始“卡顿”?或者当业务分析师要求查看变更历史时,您却只有最新状态?熟悉吗?那么欢迎来到CQRS(命令查询职责分离)和事件溯源的世界。
这两种模式不仅仅是微服务世界中的流行词汇。它们是强大的工具,能够帮助您构建可扩展、可审计且灵活的系统。是的,它们非常适合C++——一种对性能和内存控制至关重要的语言。
在本文中,我们不会进行枯燥的讲解。我们将探讨如何在纯C++中实现CQRS和事件溯源,无需框架的魔法,并提供真实示例和可直接使用的模板。
CQRS:分离读写操作
在经典的CRUD(创建、读取、更新、删除)架构中,我们使用相同的模型进行读写操作。这在系统规模不大时很方便。但当出现以下情况时,问题就来了:
- 内存中的对象过于复杂,无法快速序列化以供读取。
- 写入业务逻辑需要验证,从而拖慢读取速度。
- 系统的不同部分希望以不同方式查看相同的数据。
CQRS 提出了一个简单而巧妙的解决方案:将模型分为两部分——Command(命令)用于更改状态,Query(查询)用于读取。它们可以使用不同的存储、不同的数据结构,甚至可以在不同的机器上实现。
C++中CQRS的基本实现
让我们从简单的开始。假设我们正在编写一个订单管理系统。我们不再将所有内容存储在一个Order类中,而是分离职责。
首先,定义命令和查询的接口:
#include <memory>#include <vector>#include <string>
// 所有命令的基类class Command {public: virtual ~Command() = default; virtual void execute() = 0;};
// 所有查询的基类template <typename TResult>class Query {public: virtual ~Query() = default; virtual TResult execute() = 0;};现在,创建一个具体的命令来向订单添加商品:
class AddItemCommand : public Command {private: std::string orderId_; std::string productId_; int quantity_; // 对存储库(或命令总线)的引用 OrderRepository& repository_;
public: AddItemCommand(const std::string& orderId, const std::string& productId, int quantity, OrderRepository& repo) : orderId_(orderId), productId_(productId), quantity_(quantity), repository_(repo) {}
void execute() override { // 业务逻辑:检查商品是否有库存 if (quantity_ <= 0) { throw std::invalid_argument("数量必须为正数"); } // 调用领域服务或聚合 auto& order = repository_.getById(orderId_); order.addItem(productId_, quantity_); repository_.save(order); }};而查询订单摘要的方式如下:
class OrderSummaryQuery : public Query<OrderSummary> {private: std::string orderId_; ReadModelDatabase& db_;
public: OrderSummaryQuery(const std::string& orderId, ReadModelDatabase& db) : orderId_(orderId), db_(db) {}
OrderSummary execute() override { // 从优化的读取数据库中读取 return db_.getOrderSummary(ord