前言

  这几天在研究DDD和CQRS。快把我绕晕了。发现国外的好文质量还是挺高的。之所以先体验CQRSlite这个小框架,是因为看了一位大神写的文章:https://www.codeproject.com/articles/991648/cqrs-a-cross-examination-of-how-it-works 。于是乎,下载框架体验一下。

什么是CQRS?

  Command Query Responsibility Segregation 的简称。翻译过来就是命令查询职责分离模式。在具体的也就不由我这个小菜鸟去阐述了。根据我的理解,在项目中,我们通常做一些数据保存的工作,但是数据查询的时候可能需要联合查询多张表,为了优化查询速度会通过一些冗余字段或者缓存在或者其他方式去优化。而查询的一个流程基本和添加修改删除没有太大的关系。在平日的开发过程中,基本上,查询和增删改都放在同一个服务中。而CQRS要做的就是让他们分离。命令是命令,查询是查询。而他们之间是如何交互的呢,这就要用到 Pub/Sub 机制了。(不过框架里的实现貌似是通过反射注册一些Handler实现的)下面这个图或许可以帮你理解一下。(图片来源于上文的文章中)

CQRSlite

  其他的就不瞎扯了,我也只是通过这个轻量级框架去理解CQRS的实现。所以下面就简单介绍这个框架以及我的学习过程。

  首先看一下自带Demo,Demo很简单,就是一个增加,修改和展示。

  

  

  是不是超简单的Demo。下面我们看一下具体代码。

  首先,添加这个动作属于一个命令,那么我们就创建一个Create的命令。然后通过CommandBus发送命令。

/// <summary>
/// 【创建一个新项】命令
/// </summary>
public class CreateInventoryItem : ICommand
{
public readonly string Name; public CreateInventoryItem(Guid id, string name)
{
Id = id;
Name = name;
} public Guid Id { get; set; }
public int ExpectedVersion { get; set; }
}

  Controller层只要负责发送命令即可

 [HttpPost]
public async Task<ActionResult> Add(string name, CancellationToken cancellationToken)
{
await _commandSender.Send(new CreateInventoryItem(Guid.NewGuid(), name), cancellationToken);
return RedirectToAction("Index");
}

  当 CreateInventoryItem 这个命令发送出去之后,框架就去找匹配的命令处理器。代码如下:

    public class InventoryCommandHandlers : ICommandHandler<CreateInventoryItem>
{
private readonly ISession _session; public InventoryCommandHandlers(ISession session)
{
_session = session;
} public async Task Handle(CreateInventoryItem message)
{
var item = new InventoryItem(message.Id, message.Name);
await _session.Add(item);
await _session.Commit();
}

  然后通过Handle方法去处理这个命令。可以看到,Handle中调用了session.Add 和Commit方法。

  Add方法,就是将这个Aggregate添加到内存缓存中。用于后期版本追踪。

  _trackedAggregates = new Dictionary<Guid, AggregateDescriptor>();

  然后,Commit方法又调用了CacheRepository,CacheRepository又调用了EventStore的Save方法,看到EventStore这个词就要提起EventSourcing。其实我理解的事件朔源就是说,通过一定顺序的事件序列可以重新得到当前聚合状态。上文中的CacheRepository和EventStore都是CQRSlite框架中的实现。

//Session.cs
public async Task Commit(CancellationToken cancellationToken = default(CancellationToken))
{
var tasks = new Task[_trackedAggregates.Count];
var i = ;
foreach (var descriptor in _trackedAggregates.Values)
{
//这个_repository 是cacheRepository
tasks[i] = _repository.Save(descriptor.Aggregate, descriptor.Version, cancellationToken);
i++;
}
await Task.WhenAll(tasks).ConfigureAwait(false);
_trackedAggregates.Clear();
}
//CacheRepository.cs
public async Task Save<T>(T aggregate, int? expectedVersion = null,
CancellationToken cancellationToken = default(CancellationToken)) where T : AggregateRoot
{
var @lock = _locks.GetOrAdd(aggregate.Id, CreateLock);
await @lock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (aggregate.Id != Guid.Empty && !await _cache.IsTracked(aggregate.Id).ConfigureAwait(false))
{
await _cache.Set(aggregate.Id, aggregate).ConfigureAwait(false);
}
//这里的_repository是Domain.Repository
await _repository.Save(aggregate, expectedVersion, cancellationToken).ConfigureAwait(false);
}
catch (Exception)
{
await _cache.Remove(aggregate.Id).ConfigureAwait(false);
throw;
}
finally
{
@lock.Release();
}
}
//Repository.cs
public async Task Save<T>(T aggregate, int? expectedVersion = null, CancellationToken cancellationToken = default(CancellationToken)) where T : AggregateRoot
{
if (expectedVersion != null && (await _eventStore.Get(aggregate.Id, expectedVersion.Value, cancellationToken).ConfigureAwait(false)).Any())
{
throw new ConcurrencyException(aggregate.Id);
} var changes = aggregate.FlushUncommitedChanges();
//最后调用EventStore的Save方法。也就是只存储事件
await _eventStore.Save(changes, cancellationToken).ConfigureAwait(false); if (_publisher != null)
{
foreach (var @event in changes)
{
await _publisher.Publish(@event, cancellationToken).ConfigureAwait(false);
}
}
}
//实现IEventStore接口的自定义EventStore
public async Task Save(IEnumerable<IEvent> events, CancellationToken cancellationToken = default(CancellationToken))
{
foreach (var @event in events)
{
_inMemoryDb.TryGetValue(@event.Id, out var list);
if (list == null)
{
list = new List<IEvent>();
_inMemoryDb.Add(@event.Id, list);
}
list.Add(@event);
//调用事件发布
await _publisher.Publish(@event, cancellationToken);
}
}

  在当前的这个例子中,事件是Created

public class InventoryItemCreated : IEvent
{
public readonly string Name;
public InventoryItemCreated(Guid id, string name)
{
Id = id;
Name = name;
} public Guid Id { get; set; }
public int Version { get; set; }
public DateTimeOffset TimeStamp { get; set; }
}

  最后呢,View层接收到事件,进行处理就OK了。

 public class InventoryItemDetailView : ICancellableEventHandler<InventoryItemCreated>,
ICancellableEventHandler<InventoryItemDeactivated>,
ICancellableEventHandler<InventoryItemRenamed>,
ICancellableEventHandler<ItemsRemovedFromInventory>,
ICancellableEventHandler<ItemsCheckedInToInventory>
{
public Task Handle(InventoryItemCreated message, CancellationToken token)
{
InMemoryDatabase.Details.Add(message.Id,
new InventoryItemDetailsDto(message.Id, message.Name, , message.Version));
return Task.CompletedTask;
}

  相信小伙伴们读到这里还是一脸懵逼。没关系,上文中的简化版流程如下:

  

  由于这里是同步的,所以在视图上展示是没有什么问题的,但是真正使用的时候大部分视图展示可能由于异步处理事件更新View,所以展示上会有延迟。CQRS和ES使用上还是和普通的服务开发有些区别的。不过作为入门,我能学到的目前就这么多,里面肯定还有更大的空间去发掘。

总结

  本文不是一个CQRS的介绍,也不是一篇科普文章,只是一个小菜鸟的学习过程。有错误之处在所难免。

CQRS轻量级框架【CQRSlite】学习使用小记的更多相关文章

  1. 初步了解学习flask轻量级框架,

    关于flask我有话说 flask作为一个轻量级框架,它里面有好多扩展包需要下载,比较麻烦,而且有的时候flask需要在虚拟环境下运行,但是他的优点还是有滴 ,只要是用过Django的人,都会觉得fl ...

  2. jfinal框架教程-学习笔记

    jfinal框架教程-学习笔记 JFinal  是基于 Java  语言的极速  WEB  + ORM  开发框架,其核心设计目标是开发迅速.代码量少.学习简单.功能强大.轻量级.易扩展.Restfu ...

  3. 一个入门rpc框架的学习

    一个入门rpc框架的学习 参考 huangyong-rpc 轻量级分布式RPC框架 该程序是一个短连接的rpc实现 简介 RPC,即 Remote Procedure Call(远程过程调用),说得通 ...

  4. DDD实战进阶第一波(三):开发一般业务的大健康行业直销系统(搭建支持DDD的轻量级框架二)

    了解了DDD的好处与基本的核心组件后,我们先不急着进入支持DDD思想的轻量级框架开发,也不急于直销系统需求分析和具体代码实现,我们还少一块, 那就是经典DDD的架构,只有了解了经典DDD的架构,你才能 ...

  5. 互联网轻量级框架SSM-查缺补漏第一天

    简言:工欲其事必先利其器,作为一个大四的准毕业生,在实习期准备抽空补一下基础.SSM框架作为互联网的主流框架,在会使用的基础上还要了解其原理,我觉得会对未来的职场会有帮助的.我特意的买了一本<J ...

  6. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring框架的基本思想

    EJB的学习成本很高,开发效率却不高,需要编写很多重复的代码,这些问题阻止了EJB的继续发展.就在EJB技术止步不前的时候,Spring框架在合适的时机出现了,Spring框架和EJB不同,Sprin ...

  7. spring框架的学习->从零开始学JAVA系列

    目录 Spring框架的学习 框架的概念 框架的使用 Spring框架的引入 概念 作用 内容 SpringIOC的学习 概念 作用 基本使用流程 SpringIOC创建对象的三种方式 通过构造器方式 ...

  8. FluentData,它是一个轻量级框架,关注性能和易用性。

    http://www.cnblogs.com/zengxiangzhan/p/3250105.html FluentData,它是一个轻量级框架,关注性能和易用性. 下载地址:FlunenData.M ...

  9. (转) 基于Theano的深度学习(Deep Learning)框架Keras学习随笔-01-FAQ

    特别棒的一篇文章,仍不住转一下,留着以后需要时阅读 基于Theano的深度学习(Deep Learning)框架Keras学习随笔-01-FAQ

随机推荐

  1. 问题小记(MyBatis传参出现的小问题)

    问题一:在MyBatis中注解@Param和ParameterType不能一起用,会报错Parameter 'XXX' not found. Available parameters are [1, ...

  2. 【原创】MapReduce程序如何在集群上执行

    首先了解下资源调度管理框架Yarn. Yarn的结构(如图): Resource Manager (rm)负责调度管理整个集群上的资源,而每一个计算节点上都会有一个Node Manager(nm)来负 ...

  3. MySql中存储引擎MyISAM与InnoDB区别于选择

    InnoDB: 支持事务处理等 不加锁读取 支持外键 支持行锁 不支持FULLTEXT类型的索引 不保存表的具体行数,扫描表来计算有多少行 DELETE 表时,是一行一行的删除 InnoDB 把数据和 ...

  4. Windows下etc文件夹

    etc etcetera[ɛtsɛtərə]缩写 等等的意思 放置一些其他文件

  5. csharp: datagridview Convert csv file

    /// <summary> /// 保存文件 /// 涂聚文 /// 2014-08-29 /// Geovin Du /// </summary> /// <param ...

  6. php浮点数加减乘除bug

    项目测试阶段,少部分微信支付成功,但是在异步通知校对订单金额是否一致时,一直被认定订单金额不一致. 类似于: 浏览器输出: 分析: 因为计算机二进制无法准确表示部分浮点数(如2.03.0.58等等), ...

  7. 图片大于div时的居中显示

    当图片大于div时,想要图片居中显示,如果图片等比例缩小可能会导致图片不能填充整个div,如果直接将图片不设置宽高,将其外层div设置overflow:hidden:这时即使外层div设置了水平垂直居 ...

  8. 表单校验常用原生js库

    1.字符串去除左右空格继承形式// 除去左右空格String.prototype.Trim = function() { return this.replace(/(^\s*)|(\s*$)/g, & ...

  9. React Native之React速学教程(上)

    概述 本篇为<React Native之React速学教程>的第一篇.本篇将从React的特点.如何使用React.JSX语法.组件(Component)以及组件的属性,状态等方面进行讲解 ...

  10. 关于 class 的命名

    class名称中只出现小写字符和破折号 使用有组织或目的明确的名称,不使用表现形式 基于最近的的父class 作为新class的前缀 使用 .js-* 来标识行为,并且不要将这些class包含到css ...