经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
手撸一套纯粹的CQRS实现
来源:cnblogs  作者:thz  时间:2019/6/10 13:43:47  对本文有异议

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

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

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

使用CQRS之前:

CustomerService

  1. void MakeCustomerPreferred(CustomerId)
  2. Customer GetCustomer(CustomerId)
  3. CustomerSet GetCustomersWithName(Name)
  4. CustomerSet GetPreferredCustomers()
  5. void ChangeCustomerLocale(CustomerId, NewLocale)
  6. void CreateCustomer(Customer)
  7. void EditCustomerDetails(CustomerDetails)

使用CQRS之后:

CustomerWriteService

  1. void MakeCustomerPreferred(CustomerId)
  2. void ChangeCustomerLocale(CustomerId, NewLocale)
  3. void CreateCustomer(Customer)
  4. void EditCustomerDetails(CustomerDetails)

CustomerReadService

  1. Customer GetCustomer(CustomerId)
  2. CustomerSet GetCustomersWithName(Name)
  3. CustomerSet GetPreferredCustomers()

Query

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

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

  1. public interface IReadonlyBookRepository
  2. {
  3. IList<BookItemDto> GetBooks();
  4. BookDto GetById(string id);
  5. }

然后在Controller中使用它:

  1. public IActionResult Index()
  2. {
  3. var books = readonlyBookRepository.GetBooks();
  4. return View(books);
  5. }

Command

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

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

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

  1. public interface ICommand
  2. {
  3. }

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

  1. public interface ICommandHandler<TCommand>
  2. where TCommand : ICommand
  3. {
  4. void Execute(TCommand command);
  5. }

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

  1. public interface ICommandHandlerFactory
  2. {
  3. ICommandHandler<T> GetHandler<T>() where T : ICommand;
  4. }

ICommandHandlerFactory的实现:

  1. public class CommandHandlerFactory : ICommandHandlerFactory
  2. {
  3. private readonly IServiceProvider serviceProvider;
  4. public CommandHandlerFactory(IServiceProvider serviceProvider)
  5. {
  6. this.serviceProvider = serviceProvider;
  7. }
  8. public ICommandHandler<T> GetHandler<T>() where T : ICommand
  9. {
  10. var types = GetHandlerTypes<T>();
  11. if (!types.Any())
  12. {
  13. return null;
  14. }
  15. //实例化Handler
  16. var handler = this.serviceProvider.GetService(types.FirstOrDefault()) as ICommandHandler<T>;
  17. return handler;
  18. }
  19. //这段代码来自Diary.CQRS项目,用于查找Command对应的CommandHandler
  20. private IEnumerable<Type> GetHandlerTypes<T>() where T : ICommand
  21. {
  22. var handlers = typeof(ICommandHandler<>).Assembly.GetExportedTypes()
  23. .Where(x => x.GetInterfaces()
  24. .Any(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(ICommandHandler<>)))
  25. .Where(h => h.GetInterfaces()
  26. .Any(ii => ii.GetGenericArguments()
  27. .Any(aa => aa == typeof(T)))).ToList();
  28. return handlers;
  29. }

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

  1. public interface ICommandBus
  2. {
  3. void Send<T>(T command) where T : ICommand;
  4. }

ICommandBus的实现:

  1. public class CommandBus : ICommandBus
  2. {
  3. private readonly ICommandHandlerFactory handlerFactory;
  4. public CommandBus(ICommandHandlerFactory handlerFactory)
  5. {
  6. this.handlerFactory = handlerFactory;
  7. }
  8. public void Send<T>(T command) where T : ICommand
  9. {
  10. var handler = handlerFactory.GetHandler<T>();
  11. if (handler == null)
  12. {
  13. throw new Exception("未找到对应的处理程序");
  14. }
  15. handler.Execute(command);
  16. }
  17. }

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

  1. public class CreateBookCommand : ICommand
  2. {
  3. public CreateBookCommand(CreateBookDto dto)
  4. {
  5. this.Dto = dto;
  6. }
  7. public CreateBookDto Dto { get; set; }
  8. }

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

对应CreateBookCommand的Handler如下:

  1. public class CreateBookCommandHandler : ICommandHandler<CreateBookCommand>
  2. {
  3. private readonly IWritableBookRepository bookWritableRepository;
  4. public CreateBookCommandHandler(IWritableBookRepository bookWritableRepository)
  5. {
  6. this.bookWritableRepository = bookWritableRepository;
  7. }
  8. public void Execute(CreateBookCommand command)
  9. {
  10. bookWritableRepository.CreateBook(command.Dto);
  11. }
  12. }

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

  1. [HttpPost]
  2. public IActionResult Create(CreateBookDto dto)
  3. {
  4. dto.Id = Guid.NewGuid().ToString("N");
  5. var command = new CreateBookCommand(dto);
  6. commandBus.Send(command);
  7. return Redirect("~/book");
  8. }

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

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

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

参考文档

原文链接:http://www.cnblogs.com/youring2/p/10991338.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号