您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

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

时间:2023-10-16 13:38:28  来源:今日头条  作者:迷路的架构师

从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本质上促进了更好的安全实践。通过分离命令和查询操作,可以更容易地对写操作进行严格的验证和授权检查,同时优化读操作以提高性能。

CQRS在微服务中的应用

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

潜在的问题

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

  • 复杂性增加:引入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和事件溯源是相辅相成的,表现在以下几个方面:

  • 解耦:就像CQRS中的命令和查询解耦一样,通过事件溯源,事件(代表状态变化)与实际状态解耦,这促进了松散耦合的架构。
  • 可扩展性:CQRS中读操作和写操作的分离性与事件驱动系统非常契合。命令模型处理命令并生成事件,而查询模型处理查询并可以通过侦听这些事件进行更新。
  • 弹性:通过重播事件,可以在发生故障甚至迁移到全新系统时重新构建系统的状态。

使用Spring和Axon框架实现

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

  • 聚合根和事件处理:在Axon中,聚合根负责处理命令和生成事件。在处理完命令后,它们会应用导致状态变化的事件。
@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();
    }
}
  • 事件存储:Axon提供了一种存储和检索事件的机制。这些事件可以重播,以重建聚合根的状态。
  • 投影:Axon中的投影提供了CQRS的查询端。它们监听事件并更新读取优化的视图。这样,查询模型始终与最新的更改保持同步。

挑战和考虑因素

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

复杂性开销

  • 架构复杂性:CQRS和事件溯源引入了额外的层次和组件到系统中,如事件存储、命令和事件总线以及同步机制。
  • 学习曲线:对于不熟悉这些模式的团队来说,需要一个学习阶段。从传统的基于CRUD的系统转变思维可能是具有挑战性的。

数据一致性

  • 最终一致性:由于命令和查询模型的分隔性质,即时一致性常常被牺牲以换取最终一致性。这意味着在命令端进行的更改在查询端反映出来之前可能会有延迟。
  • 事件顺序:确保事件按照生成的顺序进行处理,特别是在分布式系统中,可能会很棘手,但对于维护一致的状态至关重要。

事件版本控制

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

  • 版本不匹配:处理同一事件类型的不同版本可能变得复杂。
  • 事件升级:随着事件的演变,系统必须能够将旧版本的事件升级到新版本,而不修改存储的事件。

数据存储和重放

  • 存储考虑:由于所有事件都被存储,事件存储库可能会迅速增长,导致存储成本增加和潜在的性能问题。
  • 重放持续时间:通过重放大量历史事件来重建系统状态可能耗时很长,影响系统的恢复和初始化时间。

其他系统集成

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

边界确定

  • 粒度决策:决定应用CQRS和事件溯源的粒度非常重要。在微观级别实施可能导致过度复杂化,而过于广泛地实施可能会削弱其好处。
  • 领域复杂性:对于简单的域来说,这些模式可能过于复杂。它们更适用于复杂的领域,其中好处超过了实施和维护成本。

工具和基础设施

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

结论

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



Tags:微服务   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  Search: 微服务  点击:(5)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  Search: 微服务  点击:(115)  评论:(0)  加入收藏
九条微服务最佳实践,你学会了哪条?
微服务之间连贯一致的代码库对于可维护性至关重要。保持代码成熟度相似,可确保系统统一演进,防止服务间出现性能、安全性和功能差异。在开发微服务时,我们需要遵循哪些最佳实践...【详细内容】
2024-01-05  Search: 微服务  点击:(98)  评论:(0)  加入收藏
Go微服务入门到容器化实践
Go微服务入门到容器化实践Go 是一门高效、现代化、快速增长的编程语言,非常适合构建 Web 应用程序。而 Docker 是一种轻量级的容器化技术,能够使得您的应用程序在任何地方运行...【详细内容】
2024-01-01  Search: 微服务  点击:(62)  评论:(0)  加入收藏
微服务全做错了!谷歌提出新方法,成本直接降为1/9!
2023,微服务“水逆”之年。长期以来,不管大厂还是小厂,微服务都被认为是云原生服务应用程序架构的事实标准,然而2023,不止那位37signals的DHH决心下云,放弃微服务,就连亚马逊和谷歌...【详细内容】
2023-12-29  Search: 微服务  点击:(118)  评论:(0)  加入收藏
微服务架构中的数据一致性
在微服务中,一个逻辑上原子操作可以经常跨越多个微服务。即使是单片系统也可能使用多个数据库或消息传递解决方案。使用多个独立的数据存储解决方案,如果其中一个分布式流程参...【详细内容】
2023-12-27  Search: 微服务  点击:(141)  评论:(0)  加入收藏
监控 Spring Cloud 微服务的实践方案
一、简介Spring Cloud是一个基于Spring Boot实现的微服务框架,它提供了丰富的微服务功能,如分布式配置、服务注册与发现、服务熔断、负载均衡等。为了更好地管理和监控这样复...【详细内容】
2023-12-19  Search: 微服务  点击:(142)  评论:(0)  加入收藏
聊聊微服务链路服务
微服务架构图片如果有用户反馈某个页面很慢,我们知道这个页面的请求调用链是 A -----> C -----> B -----> D(图片有误),怎么来定位是由哪个服务引起的问题呢? 更进一步,如果...【详细内容】
2023-12-15  Search: 微服务  点击:(123)  评论:(0)  加入收藏
选择适合微服务的编程语言,让你的工作事半功倍!
讨论编程语言就像是一场政治辩论。每个开发者都会过分捍卫他/她所使用的编程语言。然而,编程语言应该被看作是它们真正是的东西,即一种工作工具。每种编程语言都有特定的目的...【详细内容】
2023-12-14  Search: 微服务  点击:(177)  评论:(0)  加入收藏
Eureka: 微服务架构中不可或缺的服务治理工具
Eureka是Netflix开源的一款用于服务治理的工具,它是NetflixOSS(OpenSourceSoftware)项目的一部分,主要用于实现微服务架构中的服务注册与发现。在当今庞大而复杂的微服务系统中,E...【详细内容】
2023-12-14  Search: 微服务  点击:(191)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(11)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(6)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(9)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(115)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条