<返回更多

从CRUD到CQRS:使用Spring微服务转变你的架构策略

2023-10-16  今日头条  迷路的架构师
加入收藏

从CRUD到CQRS:使用Spring微服务转变你的架构策略

微服务的兴起以及现代软件架构对可扩展性、灵活性和可维护性的需求,促使开发者采用各种设计模式。近年来,命令查询责任分离(Command Query Responsibility Segregation,CQRS)模式在实践中获得大量推广。CQRS特别适用于那些命令(用于修改状态)和查询(用于读取状态)之间存在明显区别的系统。本文将深入探讨CQRS,并演示如何使用Spring微服务进行实现。

什么是 CQRS?

命令查询职责分离(CQRS)是一种架构模式,建议将数据修改操作(命令)与数据检索操作(查询)分离。这种分离允许为查询和更新数据开发专门的模型,提高应用程序的清晰度和可扩展性。

CQRS 的核心目标是通过确保每个任务负责单个操作(命令或查询,但绝不会同时负责两者)来简化任务。

起源和演进

CQRS并不是一个全新的概念。它的根源可以追溯到CQS(Command Query Separation,命令查询分离)原则,该原则由Eiffel编程语言的创建者Bertrand Meyer推广。尽管CQS主要关注方法层面,即一个方法应该执行命令或回答查询,但CQRS将这一原则扩展到了应用程序的架构层面,建议使用独立的架构组件处理命令和查询。

为什么使用CQRS?

CQRS在微服务中的应用

在分布式系统中,服务通常需要具有自治性和高度解耦,CQRS提供了一个清晰的路径。每个微服务都可以采用CQRS模式,确保它处理命令和查询的内部细节与其他服务分离。这也与领域驱动设计(DDD)非常契合,其中领域事件可以触发不同微服务中的命令操作。

潜在的问题

尽管CQRS带来了许多好处,但也存在一些挑战:

使用Spring微服务实现CQRS

Spring生态系统中丰富的工具和框架非常适合在微服务环境中实现CQRS模式。

新建一个Spring Boot项目

第一步是创建一个基本的Spring Boot项目。如果你是第一次使用Spring Boot,可以使用Spring Initializr初始化项目。可以根据自己偏好引入一些必需的依赖项包括Spring Web、Spring Data JPA、数据库连接器等。

命令、命令处理器和聚合

在基于Spring的CQRS系统中,命令表示改变某个状态的意图,而命令处理器则用于处理这些命令。

命令示例如下:

public class CreateUserCommand {
    private final String userId;
    private final String username;

    // 构造函数, getters,以及其他方法...
}

对于每个命令,都需要定义了相应的命令处理器。该处理器需要包含处理命令的实际逻辑,如下:

@Service
public class CreateUserCommandHandler implements CommandHandler<CreateUserCommand> {
    
    @Autowired
    private UserRepository userRepository;

    @Override
    public void handle(CreateUserCommand command) {
        User user = new User(command.getUserId(), command.getUsername());
        userRepository.save(user);
    }
}

在领域驱动设计(DDD)的上下文中,状态变更通常发生在聚合根上。这些聚合根需要遵循所有领域规则,然后在对数据变更进行持久化。

查询和查询处理器

类似地,查询表示读取某些状态的请求,而查询处理器则处理这些请求。

查询命令示例:

public class GetUserByIdQuery {
    private final String userId;

    // 构造函数, getters, 以及其他方法
}

对应的查询处理器:

@Service
public class GetUserByIdQueryHandler implements QueryHandler<GetUserByIdQuery, User> {
    
    @Autowired
    private UserRepository userRepository;

    @Override
    public User handle(GetUserByIdQuery query) {
        return userRepository.findById(query.getUserId()).orElse(null);
    }
}

事件溯源及Axon框架集成

尽管CQRS提供了分离的机制,但使用事件溯源可以简化在命令和查询之间维护状态的过程。Axon Framework是一个实现了CQRS和事件溯源的流行框架。

在Axon中,事件在命令处理后进行发布。这些事件可以被持久化,然后用于重新创建聚合根的状态,有助于保持查询端与命令端的同步。

使用Apache Kafka进行异步通信

考虑到微服务的分布式特性,实现服务之间的异步通信是非常有必要的。可以将Apache Kafka集成到Spring生态系统中,以实现强大的事件驱动架构,这在CQRS设置中尤其有用。

由命令端产生的事件可以推送到Kafka的主题中,查询端可以消费这些事件来更新自己的数据存储。这确保了命令端和查询端之间的解耦,使系统更具弹性和可扩展性。

事件溯源和CQRS

尽管CQRS专注于分离命令和查询的责任,但事件溯源确保将应用程序状态的每次更改捕获在事件对象中,并按照应用顺序存储在相同聚合根上。这样允许你重建过去的状态,在与CQRS结合使用时特别有优势。

事件溯源的核心思想

事件溯源是一种将领域事件持久化而不是持久化状态本身的方式。这些事件捕获状态转换。通过重新播放这些事件,可以重建聚合根的当前状态。

例如,可以存储银行账户的所有交易(像存款和提款这样的事件),而不是仅存储当前余额。通过重新播放这些事件,可以计算出当前余额。

事件溯源的好处

事件溯源与CQRS集成

CQRS和事件溯源是相辅相成的,表现在以下几个方面:

使用Spring和Axon框架实现

正如之前提到的,Axon Framework为在Spring应用程序中实现CQRS和事件溯源提供了一种无缝的方案:

@Aggregate
public class Account {
    @AggregateIdentifier
    private String accountId;
    private int balance;

    @CommandHandler
    public void handle(WithdrawMoneyCommand cmd) {
        if (cmd.getAmount() > balance) {
            throw new InsufficientFundsException();
        }
        Apply(new MoneyWithdrawnEvent(cmd.getAccountId(), cmd.getAmount()));
    }

    @EventSourcingHandler
    public void on(MoneyWithdrawnEvent evt) {
        this.balance -= evt.getAmount();
    }
}

挑战和考虑因素

尽管CQRS和事件溯源可以带来巨大的好处,但也带来了复杂性。

复杂性开销

数据一致性

事件版本控制

随着时间的推移,事件的结构或语义可能会发生变化,从而带来以下挑战:

数据存储和重放

其他系统集成

使用CQRS和事件溯源的系统与不遵循这些模式的外部系统集成可能具有挑战性,特别是在数据同步和事务管理方面。

边界确定

工具和基础设施

虽然有像Axon这样的工具和框架支持CQRS和事件溯源,但它们可能并不完全适合所有场景。可能需要进行定制实现,这可能会增加项目的复杂性和持续时间。

结论

CQRS为扩展和组织微服务提供了一种独特的方式。当与Spring生态系统结合使用时,它可以提供一个强大的工具包,用于构建健壮、可扩展和易于维护的系统。然而,就像所有架构决策一样,需要权衡利弊并确保它是否适合你的实际场景。

关键词:微服务      点击(10)
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多微服务相关>>>