MASA Framework 命令查询职责分离

概念

CQRS (https://learn.microsoft.com/zh-cn/azure/architecture/patterns/cqrs)是一种与领域驱动设计和事件溯源相关的架构模式, 它的全称是Command Query Responsibility Segregation, 又叫命令查询职责分离, Greg Young在2010年创造了这个术语, 它是基于Bertrand Meyer 的 CQS (Command-Query Separation 命令查询分离原则) 设计模式。

CQRS认为不论业务多复杂在最终实现的时候, 无非是读写操作, 因此建议将应用程序分为两个方面, 即Command(命令)和Query(查询)

  • 命令端:

    • 关注各种业务如何处理, 更新状态进行持久化

    • 不返回任何结果 (void)

  • 查询端:

    • 查询, 并从不修改数据库

CQRS的三种实现

单一数据库的CQRS

aba55d0f2344abd2ed4e3248106f1748.png

命令与读取操作的是同一个数据库, 命令端通过ORM框架将实体保存到数据库中, 查询端通过数据访问层获取数据 (数据访问层通过ORM框架或者存储过程获取数据)

双数据库的CQRS

a5c5cad987a39e2e42e72f570d490301.png

命令与读取操作的是不同的数据库, 命令端通过ORM框架将实体保存到 写库 (Write Db), 并将本地改动推送到 读库 (Read Db), 查询端通过数据访问层访问 读库 (Read Db), 使用这种模式可以带来以下好处:

  • 查询更简单

    • 读操作不需要任何的完整性校验, 也不需要外键约束, 可以减少锁争用, 我们可以针对查询端单独优化, 还可以使用刚好包含每个模板需要的数据的数据库视图,使得查询变得更快更简单

  • 提升查询端的使用体验

    • 由于这种架构将读写彻底分离,由于一般系统是读操作远远大于写操作, 这给我们的系统带来了巨大的性能提升, 极大的提升了客户的使用体验

  • 关注点分离

    • 读写分离的模型可以使得关注点分离, 使得读模型会变得相对简单

事件溯源 (Event Sourcing) CQRS

a5481fcef92663b08a3a8f944f1efdd2.png

通过事件溯源实现的CQRS中会将应用程序的改变都以事件的方式存储起来, 使用这种模式可以带来以下好处:

  • 事件存储中了完整的审计跟踪, 后续出现问题时方便跟踪

  • 可以在任何的时间点重建实体的状态, 它将有助于排查问题并修复问题

  • 提升查询端的使用体验

    • 查询端与命令端可以是完全不同的数据源, 查询端可以针对查询条件做针对应的优化, 或者使用像ESRedis等用来存储数据, 提升查询效率

  • 独立缩放

    • 命令端与查询端可以被独立缩放, 减少锁争用

当然事情有利自然也有弊, CQRS的使用固然会带来很多好处, 但同样它也会给项目带来复杂度的提升, 并且双数据库模式、事件溯源模式 (https://microservices.io/patterns/data/event-sourcing.html) 的CQRS, 使用的是最终一致性, 这些都是我们在选择技术方案时必须要考虑的

设计

上述文章中我们了解到了CQRS其本质上是一种读写分离的设计思想, 它并不是强制性的规定必须要怎样去做, 这点与之前的IEvent (进程内事件)、IIntegrationEvent (跨进程事件)不同, 它并不是强制性的, 根据CQRS的设计模式我们将事件分成CommandQuery

由于Query (查询) 是需要有返回值的, 因此我们在继承IEvent的同时, 还额外增加了一个Result属性用以存储结果, 我们希望将查询的结果保存到Result中, 但它不是强制性的, 我们并没有强制性要求必须要将结果保存到Result中。

由于Command (命令) 是没有返回值的, 因此我们并没有额外新增Result属性, 我们认为命令会更新数据, 那就需要用到工作单元, 因此Command除了继承IEvent之外, 还继承了ITransaction,这方便了我们在Handler中的可以通过@event.UnitOfWork来管理工作单元, 而不需要通过构造函数来获取

MASA Framework 

(https://github.com/masastack/MASA.Framework) 并没有要求必须使用 Event Sourcing 模式 

(https://microservices.io/patterns/data/event-sourcing.html) 或者 双数据库模式 的CQRS, 具体使用哪种实现, 它取决于业务的决策者

下面就就来看看MASA Framework提供的CQRS是如何使用的

入门

  • 安装.NET 6.0 (https://dotnet.microsoft.com/zh-cn/download/dotnet/6.0)

1.新建ASP.NET Core 空项目Assignment.CqrsDemo,并安装Masa.Contrib.Dispatcher.EventsMasa.Contrib.Dispatcher.IntegrationEventsMasa.Contrib.Dispatcher.IntegrationEvents.DaprMasa.Contrib.ReadWriteSplitting.CqrsMasa.Contrib.Development.DaprStarter.AspNetCore

dotnet new web -o Assignment.CqrsDemo
cd Assignment.CqrsDemodotnet add package Masa.Contrib.Dispatcher.Events --version 0.7.0-preview.9 //使用进程内事件总线
dotnet add package Masa.Contrib.Dispatcher.IntegrationEvents --version 0.7.0-preview.9 //使用跨进程事件总线
dotnet add package Masa.Contrib.Dispatcher.IntegrationEvents.Dapr --version 0.7.0-preview.9 //使用Dapr提供pubsub能力
dotnet add package Masa.Contrib.ReadWriteSplitting.Cqrs --version 0.7.0-preview.9 //使用CQRSdotnet add package Masa.Contrib.Development.DaprStarter.AspNetCore --version 0.7.0-preview.9  //开发环境下协助 Dapr Sidecar, 用于通过Dapr发布集成事件

 2.注册跨进程事件总线、进程内事件总线, 修改类Program.cs

示例中未真实使用DB, 不再使用发件箱模式, 只需要使用集成事件提供的PubSub (https://docs.dapr.io/zh-hans/developing-applications/building-blocks/pubsub/pubsub-overview/)能力即可

builder.Services.AddIntegrationEventBus(dispatcherOptions =>
{dispatcherOptions.UseDapr();//使用 Dapr 提供的PubSub能力dispatcherOptions.UseEventBus();//使用进程内事件总线
});

3.注册Dapr Starter协助管理Dapr Sidecar (开发环境使用)

if (builder.Environment.IsDevelopment())builder.Services.AddDaprStarter();

4.新增加添加商品方法, 修改类Program.cs

app.MapPost("/goods/add", async (AddGoodsCommand command, IEventBus eventBus) =>
{await eventBus.PublishAsync(command);
});/// <summary>
/// 添加商品参数, 用于接受商品参数
/// </summary>
public record AddGoodsCommand : Command
{public string Name { get; set; }public string Cover { get; set; }public decimal Price { get; set; }public int Count { get; set; }
}

5.新增加查询商品的方法, 修改类Program.cs

app.MapGet("/goods/{id}", async (Guid id, IEventBus eventBus) =>
{var query = new GoodsItemQuery(id);await eventBus.PublishAsync(query);return query.Result;
});/// <summary>
/// 用于接收查询商品信息参数
/// </summary>
public record GoodsItemQuery : Query<GoodsItemDto>
{public Guid Id { get; set; } = default!;public override GoodsItemDto Result { get; set; }public GoodsItemQuery(Guid id){Id = id;}
}/// <summary>
/// 用于返回商品信息
/// </summary>
public class GoodsItemDto
{public Guid Id { get; set; }public string Name { get; set; }public string Cover { get; set; }public decimal Price { get; set; }public int Count { get; set; }public DateTime DateTime { get; set; }
}

6.新增Command处理程序, 添加类CommandHandler.cs

public class CommandHandler
{/// <summary>/// 将商品添加到Db,并发送跨进程事件/// </summary>/// <param name="command"></param>/// <param name="integrationEventBus"></param>[EventHandler]public async Task AddGoods(AddGoodsCommand command, IIntegrationEventBus integrationEventBus){//todo: 模拟添加商品到db并发送添加商品集成事件var goodsId = Guid.NewGuid(); //模拟添加到db后并获取商品idawait integrationEventBus.PublishAsync(new AddGoodsIntegrationEvent(goodsId, command.Name, command.Cover, command.Price,command.Count));}
}/// <summary>
/// 跨进程事件, 发送添加商品事件
/// </summary>
/// <param name="Id"></param>
/// <param name="Name"></param>
/// <param name="Cover"></param>
/// <param name="Price"></param>
/// <param name="Count"></param>
public record AddGoodsIntegrationEvent(Guid Id, string Name, string Cover, decimal Price, int Count) : IntegrationEvent
{public Guid Id { get; set; } = Id;public string Name { get; set; } = Name;public string Cover { get; set; } = Cover;public decimal Price { get; set; } = Price;public int Count { get; set; } = Count;public override string Topic { get; set; } = nameof(AddGoodsIntegrationEvent);
}

7.新增Query处理程序, 添加类QueryHandler.cs

public class QueryHandler
{/// <summary>/// 从缓存查询商品信息/// </summary>/// <param name="query"></param>/// <returns></returns>[EventHandler]public Task GetGoods(GoodsItemQuery query){//todo: 模拟从cache获取商品var goods = new GoodsItemDto();query.Result = goods;return Task.CompletedTask;}
}

8.新增添加商品的跨进程事件的处理服务, 修改Program.cs

app.MapPost("/integration/goods/add",[Topic("pubsub", nameof(AddGoodsIntegrationEvent))](AddGoodsIntegrationEvent @event, ILogger<Program> logger) =>{//todo: 模拟添加商品到缓存logger.LogInformation("添加商品到缓存, {Event}", @event);});// 使用 dapr 来订阅跨进程事件
app.UseRouting();
app.UseCloudEvents();
app.UseEndpoints(endpoint =>
{endpoint.MapSubscribeHandler();
});

流水账式的服务会使得Program.cs变得十分臃肿, 可以通过MASA Framework提供的MinimalAPIs来简化Program.cs 点击查看详情(https://blogs.masastack.com/2022/07/12/masa/framework/practice/14.%20%E6%9C%80%E5%B0%8F%20API%20-%20MinimalAPIs)

我们上面的例子是通过事件总线来完成解耦以及数据模型的同步, 使用的双数据库模式, 但读库使用的是 缓存数据库, 在Command端做商品的添加操作, 在Query端只做查询, 且两端分别使用各自的数据源, 两者业务互不影响, 并且由于缓存数据库性能更强, 它将最大限度的提升性能, 使得我们有更好的使用体验。

在Masa Framework中仅仅是通过ICommandIQuery将读写分开, 但这并没有硬性要求, 事实上你使用IEvent也是可以的, CQRS只是一种设计模式, 这点我们要清楚, 它只是告诉我们要按照一个什么样的标准去做, 但具体怎么来做, 取决于业务的决策者, 除此之外, 后续Masa Framework还会增加对Event Sourcing(事件溯源 (https://docs.microsoft.com/zh-cn/azure/architecture/patterns/event-sourcing))的支持, 通过事件重放, 允许我们随时重建到对象的任何状态

本章源码

Assignment15

https://github.com/zhenlei520/MasaFramework.Practice

CQRS架构项目:https://github.com/masalabs/MASA.EShop/tree/main/src/Services/Masa.EShop.Services.Catalog

参考

  • CQRS 模式 

    (https://learn.microsoft.com/zh-cn/azure/architecture/patterns/cqrs)

  • 在微服务中应用简化后的 CQRS 和 DDD 模式

     (https://learn.microsoft.com/zh-cn/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/apply-simplified-microservice-cqrs-ddd-patterns)

  • .NET现代化应用开发 - CQRS&类目管理代码剖析 

    (https://www.bilibili.com/video/BV1D24y1R7jE)

开源地址

MASA.Framework:https://github.com/masastack/MASA.Framework


如果你对我们的 MASA Framework 感兴趣, 无论是代码贡献、使用、提 Issue, 欢迎联系我们

3cdd22fb67bb2b45ee75cb4ac853468c.png

1eb59f06e09a7e11b04e7130c00e0ba7.gif

《MASA Framework实战课程》已开课

点击“阅读原文”查看课程安排

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/280339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Google的Project Stream准备在Chrome中播放AAA控制台游戏

Streaming full 3D games over a high-speed web connection is a fast growing trend. And with ridiculous amounts of infrastructure and remote computing power, Google is well equipped to join it. 通过高速网络连接流式传输完整的3D游戏是一种快速增长的趋势。 凭借可…

私有云之迷思:未来是什么?

本文讲的是私有云之迷思&#xff1a;未来是什么&#xff1f;&#xff0c;【编者的话】非常好的一篇文章&#xff0c;作者从OpenStack目前的困境讲起&#xff0c;聊到了私有云的产生背景&#xff0c;进而介绍了云计算的发展史。从云计算诞生的初衷以及现在流行的分布式应用又延伸…

如何在vue中使用sass

安装sass 安装教程链接&#xff1a; https://www.sass.hk/install/ 在vue中使用sass 参考链接&#xff1a; https://www.jianshu.com/p/8e60048baeb7 打开控制台&#xff1a;输入命令行 如果是没有淘宝镜像的&#xff0c;先下载淘宝镜像&#xff0c;之后的下载速度比较快 npm i…

maven项目的目录结构

1、maven项目采用“约定优于配置”的原则&#xff1a; src/main/java&#xff1a;约定用于存放源代码&#xff0c;src/test/java&#xff1a;用于存放单元测试代码&#xff0c;&#xff08;测试代码的包应该和被测试代码包结构保持一致&#xff0c;方便测试查找&#xff09;src…

AWS大力支持.NET 开源项目,和Azure抢.NET 客户

出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013)在 2022 re:Invent 会议上&#xff0c; AWS 软件开发经理 Saikat Banerjee 锐评道&#xff1a;” 我们发现 .NET 开源项目资金严重不足&#xff0c;仍可称之为第三方开源”。随即表示 AWS 过去非常重视 .net 生态&…

攻防 logmein_如何使用LogMeIn Hamachi在任何地方访问文件

攻防 logmeinWhether you’re at work and forgot some file on your home computer, want to play some music on a train, or just want to move some files between your computers, accessing your files from anywhere is a life saver. 无论您是在工作时忘记了家用计算机…

Docker-machine创建虚机时停在虚机启动的提示上,并且创建的虚机显示Ip Not found...

Docker-machine创建虚机时停在虚机启动的提示上&#xff0c;并且创建的虚机用docker-machine ls 列出来的时候显示Ip Not found&#xff0c; 是什么原因那&#xff1f; 【答案】 看这个帖子&#xff1a; https://github.com/docker/machine/issues/3832 拷贝如下&#xff1a; I…

【年度总结】2016年年度总结

早晨醒来&#xff0c;在被窝里面刷着简书&#xff0c;看到一篇文章叫《深漂一年&#xff0c;一个资深程序员的2016年终告白》&#xff0c;写的很好&#xff0c;很有感触。在2016年的农历的最后一天&#xff0c;总是有很多感触要写下来。所以下午扫墓之后&#xff0c;我也按照剧…

在FC中如何获取fcdot文件

在FlexiCapture中一些客户在问如何获取.fcdot文件(在测试序列号下或者没有测试模板的情况下) 第一步&#xff1a; 1、查看License Manager查看是否找到序列号 首先我们在开始菜单里面打开ABByyFlexiCapTure11——》选择"工具"下的License Manager 第二步 1、选择管理…

Blazor学习之旅 (9) 用MudBlazor重构Todo

【Blazor】| 总结/Edison Zhou大家好&#xff0c;我是Edison。在之前的学习之旅&#xff08;3&#xff09;开发一个Todo应用中&#xff0c;我们开发了一个简单版的Todo&#xff0c;这次我们基于MudBlazor来重构这个Todo应用。Todo V1回顾在Blazor入门学习&#xff08;3&#xf…

50多种在Photoshop中删除图像背景的工具和技术,第3页

We’re completing the 50 Tools and Techniques today with this final installment. Read about advanced selection and masking tools, as well as some stupid graphics geek tricks, and ways to fake removing a background in seconds. 我们今天最后一部分将完成50多种…

socket跟TCP/IP 的关系,单台服务器上的并发TCP连接数可以有多少

常识一&#xff1a;文件句柄限制 在linux下编写网络服务器程序的朋友肯定都知道每一个tcp连接都要占一个文件描述符&#xff0c;一旦这个文件描述符使用完了&#xff0c;新的连接到来返回给我们的错误是“Socket/File:Cantopen so many files”。 这时你需要明白操作系统对可以…

SSPL的MongoDB再被抛弃,GUN Health也合流PostgreSQL

2019 年 2 月 12 日&#xff0c;红帽官方发博称&#xff0c;Red Hat Satellite 将拥抱PostgreSQL&#xff0c;并且不会支持 SSPL 许可的 MongoDB 新版本。无独有偶&#xff0c;同一天GNU Health也发博称GNU Health Federation Information System 将从MongoDB迁移到PostgreSQL&…

开源的 .NET 数据库迁移框架

你好&#xff0c;这里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;实用的工具和组件&#xff0c;希望对您有用&#xff01;简介FluentMigrator 是一个开源的数据库迁移框架&#xff0c;可以帮助用户在开发过程中保持数据库的一致性。它提供了一个简洁的 Flue…

在deepin上安装YouCompleteMe

详细安装步骤在github上有&#xff0c;https://github.com/Valloric/YouCompleteMe&#xff0c;我这里是自己总结的简化版安装步骤。 步骤1.安装Vundle 首先&#xff0c;clone到本地 git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim把以下内容…

2015年IT领域里Docker和其它颠覆性的趋势

本文讲的是2015年IT领域里Docker和其它颠覆性的趋势&#xff0c;【编者的话】文中作者介绍了2015年IT领域的一些颠覆性的趋势&#xff0c;比如Docker将如何革新PaaS、IaaS等&#xff0c;Docker将如何颠覆虚拟化、私有云、配置管理。 2014年真是令人兴奋的一年&#xff0c;这一年…

进化:从孤胆极客到高效团队_极客狂:为什么这么多的网站无法使用打印样式表?...

进化:从孤胆极客到高效团队It never ceases to amaze me that people have to look for a link or a button that says “Print” on a web page, especially considering there’s a miracle technology that makes that step unnecessary. Sadly almost nobody uses it, even…

Iterator 和 for...of 循环

Iterator 和 for...of 循环 Iterator&#xff08;遍历器&#xff09;意义 为Array、Object、Map、Set四种数据集合&#xff0c;提供统一的接口机制来处理所有不同的数据结构 。 任何数据结构&#xff0c;只要部署 Iterator 接口&#xff0c;就可以完成遍历操作&#xff08;即依…

python简单开发接口

1、首先需要安装flask这个模块&#xff1a;pip install flask。flask是个轻量级的接口开发框架2、开发接口有什么作用  1、mock接口&#xff0c;模拟一些接口&#xff0c;在别的接口没有开发好的时候&#xff0c;需要用mock去模拟一些接口。  2、知道接口是怎么开发的&…

九哥聊Kestrel网络编程第二章:开发一个Fiddler

推荐序之前在.NET 性能优化群内交流时&#xff0c;我们发现很多朋友对于高性能网络框架有需求&#xff0c;需要创建自己的消息服务器、游戏服务器或者物联网网关。但是大多数小伙伴只知道 DotNetty&#xff0c;虽然 DotNetty 是一个非常优秀的网络框架&#xff0c;广泛应用于各…