首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >手撸一套纯粹的CQRS实现

手撸一套纯粹的CQRS实现

作者头像
拓荒者IT
发布于 2019-09-24 07:16:08
发布于 2019-09-24 07:16:08
7790
举报

关于CQRS,在实现上有很多差异,这是因为CQRS本身很简单,但是它犹如潘多拉魔盒的钥匙,有了它,读写分离、事件溯源、消息传递、最终一致性等都被引入了框架,从而导致CQRS背负了太多的混淆。本文旨在提供一套简单的CQRS实现,不依赖于ES、Messaging等概念,只关注CQRS本身。

CQRS的本质是什么呢?我的理解是,它分离了读写,为读写使用不同的数据模型,并根据职责来创建相应的读写对象;除此之外其它任何的概念都是对CQRS的扩展。

下面的伪代码将展示CQRS的本质:

使用CQRS之前:

CustomerService

代码语言:javascript
AI代码解释
复制
void MakeCustomerPreferred(CustomerId) 
Customer GetCustomer(CustomerId) 
CustomerSet GetCustomersWithName(Name) 
CustomerSet GetPreferredCustomers() 
void ChangeCustomerLocale(CustomerId, NewLocale) 
void CreateCustomer(Customer) 
void EditCustomerDetails(CustomerDetails)

使用CQRS之后:

CustomerWriteService

代码语言:javascript
AI代码解释
复制
void MakeCustomerPreferred(CustomerId) 
void ChangeCustomerLocale(CustomerId, NewLocale) 
void CreateCustomer(Customer) 
void EditCustomerDetails(CustomerDetails)

CustomerReadService

代码语言:javascript
AI代码解释
复制
Customer GetCustomer(CustomerId) 
CustomerSet GetCustomersWithName(Name) 
CustomerSet GetPreferredCustomers()

Query

查询(Query): 返回结果,但是不会改变对象的状态,对系统没有副作用。

查询的实现比较简单,我们首先定义一个只读的仓储:

代码语言:javascript
AI代码解释
复制
public interface IReadonlyBookRepository
{
    IList<BookItemDto> GetBooks();

    BookDto GetById(string id);
}

然后在Controller中使用它:

代码语言:javascript
AI代码解释
复制
public IActionResult Index()
{
    var books = readonlyBookRepository.GetBooks();

    return View(books);
}

Command

命令(Command): 不返回任何结果(void),但会改变对象的状态。

命令代表用户的意图,包含业务数据。

首先定义ICommand接口,该接口不含任何方法和属性,仅作为标记来使用。

代码语言:javascript
AI代码解释
复制
public interface ICommand
{
    
}

与Command对应的有一个CommandHandler,Handler中定义了具体的操作。

代码语言:javascript
AI代码解释
复制
public interface ICommandHandler<TCommand>
    where TCommand : ICommand
{
    void Execute(TCommand command);
}

为了能够封装Handler的定位,我们还需要定一个ICommandHandlerFactory:

代码语言:javascript
AI代码解释
复制
public interface ICommandHandlerFactory
{
    ICommandHandler<T> GetHandler<T>() where T : ICommand;
}

ICommandHandlerFactory的实现:

代码语言:javascript
AI代码解释
复制
public class CommandHandlerFactory : ICommandHandlerFactory
{
    private readonly IServiceProvider serviceProvider;

    public CommandHandlerFactory(IServiceProvider serviceProvider) 
    {
        this.serviceProvider = serviceProvider;
    }

    public ICommandHandler<T> GetHandler<T>() where T : ICommand
    {
        var types = GetHandlerTypes<T>();
        if (!types.Any())
        {
            return null;
        }
        
        //实例化Handler
        var handler = this.serviceProvider.GetService(types.FirstOrDefault()) as ICommandHandler<T>;
        return handler;
    }

    //这段代码来自Diary.CQRS项目,用于查找Command对应的CommandHandler
    private IEnumerable<Type> GetHandlerTypes<T>() where T : ICommand
    {
        var handlers = typeof(ICommandHandler<>).Assembly.GetExportedTypes()
            .Where(x => x.GetInterfaces()
                .Any(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(ICommandHandler<>)))
                .Where(h => h.GetInterfaces()
                    .Any(ii => ii.GetGenericArguments()
                        .Any(aa => aa == typeof(T)))).ToList();


        return handlers;
    }

然后我们定义一个ICommandBus,ICommandBus通过Send方法来发送命令和执行命令。定义如下:

代码语言:javascript
AI代码解释
复制
public interface ICommandBus
{
    void Send<T>(T command) where T : ICommand;
}

ICommandBus的实现:

代码语言:javascript
AI代码解释
复制
public class CommandBus : ICommandBus
{
    private readonly ICommandHandlerFactory handlerFactory;

    public CommandBus(ICommandHandlerFactory handlerFactory)
    {
        this.handlerFactory = handlerFactory;
    }

    public void Send<T>(T command) where T : ICommand
    {
        var handler = handlerFactory.GetHandler<T>();
        if (handler == null)
        {
            throw new Exception("未找到对应的处理程序");
        }

        handler.Execute(command);
    }
}

我们来定一个新增命令CreateBookCommand:

代码语言:javascript
AI代码解释
复制
public class CreateBookCommand : ICommand
{
    public CreateBookCommand(CreateBookDto dto)
    {
        this.Dto = dto;
    }

    public CreateBookDto Dto { get; set; }
}

我不知道这里直接使用DTO对象来初始化是否合理,我先这样来实现

对应CreateBookCommand的Handler如下:

代码语言:javascript
AI代码解释
复制
public class CreateBookCommandHandler : ICommandHandler<CreateBookCommand>
{
    private readonly IWritableBookRepository bookWritableRepository;

    public CreateBookCommandHandler(IWritableBookRepository bookWritableRepository)
    {
        this.bookWritableRepository = bookWritableRepository;
    }

    public void Execute(CreateBookCommand command)
    {
        bookWritableRepository.CreateBook(command.Dto);
    }
}

当我们在Controller中使用时,代码是这样的:

代码语言:javascript
AI代码解释
复制
[HttpPost]
public IActionResult Create(CreateBookDto dto)
{
    dto.Id = Guid.NewGuid().ToString("N");
    var command = new CreateBookCommand(dto);
    commandBus.Send(command);

    return Redirect("~/book");
}

UI层不需要了解Command的执行过程,只需要将命令通过CommandBus发送出去即可,对于前端的操作也很简洁。

该实例的完整代码在github上,感兴趣的朋友请移步>>https://github.com/qifei2012/sample_cqrs

如果代码中有错误或不合适的地方,请在评论中指出,谢谢支持。

参考文档

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-06-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
CQRS架构实战
2.什么是CQRS 这里只通过Udi Dahan的《Clarified CQRS》文章中的一张图片简要介绍一下:
IT云清
2021/12/06
8730
CQRS架构实战
告别MediatR:构建极简CQRS架构的终极指南
Jimmy Bogard近期宣布,MediatR将对达到一定规模的企业采用商业许可模式。这一变化促使许多团队重新评估其使用方案,并开始寻找替代方案。
郑子铭
2025/07/24
2460
告别MediatR:构建极简CQRS架构的终极指南
Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
本文示例项目仓库:https://github.com/whuanle/HangfireDemo
痴者工良
2025/04/19
3350
Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
CQRS+ES项目解析-Diary.CQRS
在《当我们在讨论CQRS时,我们在讨论些神马》中,我们讨论了当使用CQRS的过程中,需要关心的一些问题。其中与CQRS关联最为紧密的模式莫过于Event Sourcing了,CQRS与ES的结合,为我们构造高性能、可扩展系统提供了基本思路。本文将介绍 Kanasz Robert在《Introduction to CQRS》中的示例项目Diary.CQRS。
拓荒者IT
2019/09/23
8960
CQRS+ES项目解析-Diary.CQRS
CQRS框架:AxonFramework 之 Hello World
Command Query Responsibility Segregation,CQRS 这个架构好象最近博客园里讨论得比较多,有几篇园友的文章很有深度,推荐阅读: CQRS架构简介 浅谈命令查询职责分离(CQRS)模式 DDD CQRS架构和传统架构的优缺点比较 比较有趣的是,以往一断谈及架构思路、OO这些,往往都是java大佬们的专长,而CQRS这个话题,好象.NET占了上风。园友汤雪华的ENODE开源大作,在github上人气也很旺。 于是,我逆向思路搜索了下java的类似项目,果然有一个Axon
菩提树下的杨过
2018/01/18
2K0
CQRS框架:AxonFramework 之 Hello World
浅谈命令查询职责分离(CQRS)模式
在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体。在一些业务逻辑简单的系统中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能问题。虽然在DB上可以做一些读写分离的设计,但在业务上如果在读写方面混合在一起的话,仍然会出现一些问题。 本文介绍了命令查询职责分离模式(Command Query Responsibility Segregation,CQRS),该模式从业务上分离修改 (Command,增,删,改,会对系统状态进行修改)和查
逸鹏
2018/04/11
2.3K0
浅谈命令查询职责分离(CQRS)模式
.NET Core 使用MediatR CQRS模式
CQRS(Command Query Responsibility Segregation)命令查询职责分离模式,它主要从我们业务系统中进行分离出我们(Command 增、删、改)和(Query 查),
HueiFeng
2020/04/09
6550
聊聊go.cqrs的Dispatcher
go.cqrs的Dispatcher接口定义了Dispatch、RegisterHandler方法;InMemoryDispatcher定义了map[string]CommandHandler属性,其Dispatch方法根据command.CommandType()获取handler,然后执行handler.Handle(command);其RegisterHandler方法遍历commands,然后获取command的type,挨个注册到map[string]CommandHandler中。
code4it
2021/04/09
3320
聊聊go.cqrs的Dispatcher
微服务实战(七):落地微服务架构到直销系统(实现命令与命令处理器)
我们先来看看CQRS架构,你对下图的架构还有印象吗?每个组件的功能都还清楚吗?如果有疑问,请查考文章《微服务实战(五):落地微服务架构到直销系统(构建高性能大并发系统)》。
用户1910585
2018/10/18
9090
微服务实战(七):落地微服务架构到直销系统(实现命令与命令处理器)
AOP:使用命令模式实现AOP
某位大牛说过,采用命名模式的好处是,你可以将命令按照不同的方式执行,如:排队、异步、远程和拦截等等。今天我介绍一下如何拦截命令的执行,这有些AOP的味道。
全栈程序员站长
2022/07/05
4340
AOP:使用命令模式实现AOP
Message=ObjectManager 地址信息无效 程序中有问题
System.Runtime.Serialization.SerializationException
用户7737280
2024/08/22
2770
Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
本文示例项目仓库:https://github.com/whuanle/HangfireDemo
郑子铭
2025/06/07
2870
Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
了解 CQRS 模式的优点、缺点以及在springboot中的简单应用
命令查询责任分离(CQRS)是一种强大的架构模式,它将软件系统中处理命令和查询的责任分开。通过划分这些关注点,CQRS 可提高可扩展性、可维护性和灵活性。在这篇文章中,我们将深入探讨 CQRS 模式,讨论其优缺点,并提供一个使用 Spring Boot 的完整案例。
用户4235284
2023/11/08
2.6K0
.NET Core 使用MediatR CQRS模式
CQRS(Command Query Responsibility Segregation)命令查询职责分离模式,它主要从我们业务系统中进行分离出我们(Command 增、删、改)和(Query 查), 同时他可以明确的区分我们每一个动作向我们的请求模型和响应模型.从而降低了我们系统的复杂性.
HueiFeng
2020/04/10
1.8K1
.NET Core 使用MediatR CQRS模式
【愚公系列】2023年01月 .NET CORE工具案例-基于MediatR的CQRS模式
CQRS 是一种与领域驱动设计 (DDD) 和事件溯源相关的架构模式,本质上是一种读写逻辑分离的机制。
愚公搬代码
2023/03/16
7560
【愚公系列】2023年01月 .NET CORE工具案例-基于MediatR的CQRS模式
C# 如何实现一个事件总线
Event Bus(事件总线)是一种用于在应用程序内部或跨应用程序组件之间进行事件通信的机制。
郑子铭
2024/01/17
4680
C# 如何实现一个事件总线
利用 Watermill 实现 Golang CQRS
CQRS 的意思是“命令-查询责任隔离”。我们分离了命令(写请求)和查询(读请求)之间的责任。写请求和读请求由不同的对象处理。
为少
2021/05/27
1.1K0
利用 Watermill 实现 Golang CQRS
.NET 云原生架构师训练营(权限系统 代码实现 WebApplication)--学习笔记
创建 ResourceController,通过 ResourceManager 获取所有 Resource
郑子铭
2022/02/21
3730
.NET 云原生架构师训练营(权限系统 代码实现 WebApplication)--学习笔记
.NET 源代码自动生成
在这篇文章中,我们将探索如何使用.NET 5中的新source generator特性,使用MediatR库和CQRS模式自动为系统生成API。
郑子铭
2024/02/27
4660
.NET 源代码自动生成
命令和查询责任隔离(CQRS)模式
通过使用单独的接口将读取数据的操作与更新数据的操作隔离开来。这可以最大化性能、可伸缩性和安全性。通过更高的灵活性支持系统随时间的发展,并防止更新命令在域级别引起合并冲突。
35岁程序员那些事
2020/02/24
1.2K0
推荐阅读
相关推荐
CQRS架构实战
更多 >
领券
社区新版编辑器体验调研
诚挚邀请您参与本次调研,分享您的真实使用感受与建议。您的反馈至关重要,感谢您的支持与参与!
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
首页
学习
活动
专区
圈层
工具
MCP广场
首页
学习
活动
专区
圈层
工具
MCP广场