手撸一套纯粹的CQRS实现
关于CQRS,在实现上有很多差异,这是因为CQRS本身很简单,但是它犹如潘多拉魔盒的钥匙,有了它,读写分离、事件溯源、消息传递、最终一致性等都被引入了框架,从而导致CQRS背负了太多的混淆。本文旨在提供一套简单的CQRS实现,不依赖于ES、Messaging等概念,只关注CQRS本身。
CQRS的本质是什么呢?我的理解是,它分离了读写,为读写使用不同的数据模型,并根据职责来创建相应的读写对象;除此之外其它任何的概念都是对CQRS的扩展。
下面的伪代码将展示CQRS的本质:
使用CQRS之前:
CustomerService
void MakeCustomerPreferred(CustomerId)
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)
使用CQRS之后:
CustomerWriteService
void MakeCustomerPreferred(CustomerId)
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)
CustomerReadService
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
Query
查询(Query): 返回结果,但是不会改变对象的状态,对系统没有副作用。
查询的实现比较简单,我们首先定义一个只读的仓储:
public interface IReadonlyBookRepository
{
IList<BookItemDto> GetBooks();
BookDto GetById(string id);
}
然后在Controller中使用它:
public IActionResult Index()
{
var books = readonlyBookRepository.GetBooks();
return View(books);
}
Command
命令(Command): 不返回任何结果(void),但会改变对象的状态。
命令代表用户的意图,包含业务数据。
首先定义ICommand接口,该接口不含任何方法和属性,仅作为标记来使用。
public interface ICommand
{
}
与Command对应的有一个CommandHandler,Handler中定义了具体的操作。
public interface ICommandHandler<TCommand>
where TCommand : ICommand
{
void Execute(TCommand command);
}
为了能够封装Handler的定位,我们还需要定一个ICommandHandlerFactory:
public interface ICommandHandlerFactory
{
ICommandHandler<T> GetHandler<T>() where T : ICommand;
}
ICommandHandlerFactory的实现:
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方法来发送命令和执行命令。定义如下:
public interface ICommandBus
{
void Send<T>(T command) where T : ICommand;
}
ICommandBus的实现:
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:
public class CreateBookCommand : ICommand
{
public CreateBookCommand(CreateBookDto dto)
{
this.Dto = dto;
}
public CreateBookDto Dto { get; set; }
}
我不知道这里直接使用DTO对象来初始化是否合理,我先这样来实现
对应CreateBookCommand的Handler如下:
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中使用时,代码是这样的:
[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
如果代码中有错误或不合适的地方,请在评论中指出,谢谢支持。
参考文档
- https://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html
- http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/
手撸一套纯粹的CQRS实现的更多相关文章
- 2w字长文!手撸一套 Java 基础面试题
Java 基础篇 Java 有哪些特点 并发性的: 你可以在其中执行许多语句,而不必一次执行它 面向对象的:基于类和面向对象的编程语言. 独立性的: 支持一次编写,到处运行的独立编程语言,即编译后的代 ...
- php手撸轻量级开发(一)
聊聊本文内容 之前讲过php简单的内容,但是原生永远是不够看的,这次用框架做一些功能性的事情. 但是公司用自己的框架不能拿出来,用了用一些流行的框架比如tp,larveral之类的感觉太重,CI也不顺 ...
- Haskell手撸Softmax回归实现MNIST手写识别
Haskell手撸Softmax回归实现MNIST手写识别 前言 初学Haskell,看的书是Learn You a Haskell for Great Good, 才刚看到Making Our Ow ...
- 第二篇-用Flutter手撸一个抖音国内版,看看有多炫
前言 继上一篇使用Flutter开发的抖音国际版 后再次撸一个国内版抖音,大部分功能已完成,主要是Flutter开发APP速度很爽, 先看下图 项目主要结构介绍 这次主要的改动在api.dart 及 ...
- 手撸Router,还要啥Router框架?react-router/vue-router躺一边去
有没有发现,在大家使用React/Vue的时候,总离不开一个小尾巴,到哪都得带着他,那就是react-router/vue-router,而基于它们的第三方框架又出现很多个性化约定和扩展,比如nuxt ...
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- 【手撸一个ORM】MyOrm的使用说明
[手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...
- 康少带你手撸orm
orm 什么是orm? 对象关系映射: 一个类映射成一张数据库的表 类的对象映射成数据库中的一条条数据 对象点数据映射成数据库某条记录的某个值 优点:不会写sql语句的程序员也可以很6的操作sql语句 ...
- 手写一套迷你版HTTP服务器
本文主要介绍如何通过netty来手写一套简单版的HTTP服务器,同时将关于netty的许多细小知识点进行了串联,用于巩固和提升对于netty框架的掌握程度. 服务器运行效果 服务器支持对静态文件css ...
随机推荐
- XML文件的一些操作
XML 是被设计用来传输和存储数据的, XML 必须含有且仅有一个 根节点元素(没有根节点会报错) 源码下载 http://pan.baidu.com/s/1ge2lpM7 好了,我们 先看一个 XM ...
- Flask 学习系列(三)---Jinjia2使用过滤器
再Jinjia2中过滤器是一种转变变量输出内容的技术.··过滤器通过管道符号“|与变量链接,并且可以通过圆括号传递参数” .举例说明: {{my_variable|default('my_variab ...
- 常用API(Object、String、StringBuffer、用户登陆注册)
常用API 今日内容介绍 u Object u String u StringBuilder 第1章 Java的API及Object类 在以前的学习过程中,我们都在学习对象基本特征.对象的使用以及对象 ...
- 函数补充:动态参数,函数嵌套,global与nonlocal关键
一丶动态参数 1.*args 位置参数,动态传参 def func(*food): print(food) print(func("米饭","馒头"," ...
- js push(),pop(),shift(),unshift()
以前没有太在意这些,这几天看<Javascript 设计模式与开发实践>(不得不说这是一本好书) 发现总是会用到这几个函数,可是有什么区别呢?? 简单来说是两套东西(数据结构时老师详细的讲 ...
- PLSQL Developer 12 保存登录的用户名和密码
1. 登录 PLSQL Developer PLSQL Developer > Preferences 2. Preferences > Logon History > Defini ...
- Cookie 没你不行
Cookie 没你不行 Cookie 没你不行 前言: Cookie 是什么 起源 到底是什么? 使用场景 如何使用cookie Cookie 和http协议 (服务端操作cookie) Cookie ...
- SAP ERP classification和C4C的同步
在ERP里创建两个characteristic: 创建一个class包这两个characteristic.Class type 002意为该class能用于equipment. replicate到C ...
- 文本框复制代码,兼容大部分浏览器(ZeroClipboard插件、附件)
;;list-style-type:none;} a,img{;} body{font:12px/180% Arial, Helvetica, sans-serif ,"新宋体"; ...
- wxWidgets:处理wxEVT
我们仍然以继承于wxFrame的MyFrame作为例子. MyFrame.h: class MyFrame : public wxFrame { ...... private: ...... void ...