【说明:博主采用边写边思考的方式完成这一系列的博客,所以代码以附件为准,文中代码仅为了说明。】

结构

在学习和实现CQRS的过程中,首要参考的项目是这个【http://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html】。所以Dpfb.Cqrs中的整体结构都是参考这个例子来的,在这个基础之上添加和改进。总的来说,.Cqrs项目的整体结构如下所示:

主要包含了(命令,事件,通信,命令处理,事件处理这几个方面)。具体的角色则如下图所示:

通信中包含了事件总线和命令总线。由于查询入口(QueryEntry)如何处置暂时没想好,所以先放一个文件夹卖萌。

实现

此时的目标是实现一个最小单元的CQRS(主要是命令和事件部分)。其中命令总线和事件总线的实现比较固定,他们的职责就是为命令和事件找到对应的处理类,然后依次调用对象的接口方法,从而将一般的直接调用“打断”,同时提供各种额外操作的注入点。为了更好的控制他们如何寻找处理类,这里引入两个接口ICommandHandlerSearcher以及IEventHandlerSearcher。放在项目的Configuration名称空间下面。现在,可以为这些接口提供一个基础的实现,供测试使用。以下是接口定义:

  1. public interface ICommand
  2. {
  3. Guid Id { get; set; }
  4. }
  5.  
  6. public interface IEvent
  7. {
  8. Guid Id { get; set; }
  9. }
  10.  
  11. public interface IEventHandler<T> where T : IEvent
  12. {
  13. void Handle(T @event);
  14. }
  15.  
  16. public interface ICommandHandler<T> where T: ICommand
  17. {
  18. void Execute(T command);
  19. }
  20.  
  21. public interface ICommandBus
  22. {
  23. void Send<T>(T command) where T : ICommand;
  24. }
  25.  
  26. public interface IEventBus
  27. {
  28. void Publish<T>(T @event) where T : IEvent;
  29. }
  30.  
  31. public interface ICommandHandlerSearcher
  32. {
  33. ICommandHandler<T> Find<T>() where T : ICommand;
  34. }
  35.  
  36. public interface IEventHandlerSearcher
  37. {
  38. IEnumerable<IEventHandler<T>> Find<T>() where T : IEvent;
  39. }

接口定义

以及通信(Buses)部分的实现:

  1. public class DpfbEventBus : IEventBus
  2. {
  3. void IEventBus.Publish<T>(T @event)
  4. {
  5. foreach (var handler in EventHandlerSearcher.Find<T>())
  6. {
  7. handler.Handle(@event);
  8. }
  9. }
  10.  
  11. public IEventHandlerSearcher EventHandlerSearcher { get; set; }
  12. }
  13.  
  14. public class DpfbCommandBus : ICommandBus
  15. {
  16. void ICommandBus.Send<T>(T command)
  17. {
  18. var handler = CommandHandlerSearcher.Find<T>();
  19. handler.Execute(command);
  20. }
  21.  
  22. public ICommandHandlerSearcher CommandHandlerSearcher { get; set; }
  23. }

Buses实现

其中,两个ISearcheres的实现包含了如何寻找Handlers以及到哪里寻找Handlers,这些具体的内容,.Cqrs这层其实并不关心。处于测试的目的,将这些实现代码移到Test名称空间下。并实现本程序集内的查找:

  1. public class TestCommandHandlerSearcher:ICommandHandlerSearcher
  2. {
  3. ICommandHandler<T> ICommandHandlerSearcher.Find<T>()
  4. {
  5. var assembly = Assembly.GetCallingAssembly();
  6. var declaredType = typeof (ICommandHandler<T>);
  7. var handlers = assembly.GetTypes()
  8. .Where(i => declaredType.IsAssignableFrom(i))
  9. .Select(i => Activator.CreateInstance(i))
  10. .ToArray();
  11. if (handlers.Count() != )
  12. throw new Exception();
  13. return handlers.First() as ICommandHandler<T>;
  14. }
  15. }
  16.  
  17. public class TestEventHandlerSearcher : IEventHandlerSearcher
  18. {
  19. IEnumerable<IEventHandler<T>> IEventHandlerSearcher.Find<T>()
  20. {
  21. var assembly = Assembly.GetCallingAssembly();
  22. var declaredType = typeof (IEventHandler<T>);
  23. var handlers = assembly.GetTypes()
  24. .Where(i => declaredType.IsAssignableFrom(i))
  25. .Select(i => Activator.CreateInstance(i));
  26. return handlers.Cast<IEventHandler<T>>();
  27. }
  28. }

Searchers实现

测试

接下来添加一些实现代码,以进行测试:

  1. public class TestEventTwo : IEvent
  2. {
  3. Guid IEvent.Id
  4. {
  5. get { throw new NotImplementedException(); }
  6. set { throw new NotImplementedException(); }
  7. }
  8. }
  9.  
  10. public class TestEventOne : IEvent
  11. {
  12. Guid IEvent.Id
  13. {
  14. get { throw new NotImplementedException(); }
  15. set { throw new NotImplementedException(); }
  16. }
  17. }
  18.  
  19. public class TestEventHandler : IEventHandler<TestEventOne>,
  20. IEventHandler<TestEventTwo>
  21. {
  22. void IEventHandler<TestEventOne>.Handle(TestEventOne @event)
  23. {
  24. Console.WriteLine("处理了事件eventOne");
  25. Configuration.EventBus.Publish(new TestEventTwo());
  26. Console.WriteLine("引发了事件eventOne");
  27. }
  28.  
  29. void IEventHandler<TestEventTwo>.Handle(TestEventTwo @event)
  30. {
  31. Console.WriteLine("处理了事件eventTwo");
  32. }
  33. }
  34.  
  35. public class TestCommandHandler : ICommandHandler<TestCommand>
  36. {
  37. void ICommandHandler<TestCommand>.Execute(TestCommand command)
  38. {
  39. Console.WriteLine("获取并处理消息:" + command.Message);
  40. var eventOne = new TestEventOne();
  41. Configuration.EventBus.Publish(eventOne);
  42. }
  43. }
  44.  
  45. public class TestCommand : ICommand
  46. {
  47. Guid ICommand.Id
  48. {
  49. get { throw new NotImplementedException(); }
  50. set { throw new NotImplementedException(); }
  51. }
  52.  
  53. public string Message { get; set; }
  54. }
  55.  
  56. public static class Configuration
  57. {
  58. public static ICommandBus CommandBus = new DpfbCommandBus();
  59.  
  60. public static IEventBus EventBus = new DpfbEventBus();
  61. }

测试代码

其中,Buses作为单例存在于一个静态类的字段中。
最后,用一个单元测试运行:

  1. [TestMethod]
  2. public void TestMethod1()
  3. {
  4. var command = new TestCommand {Message = "国庆记得回家"};
  5. Configuration.CommandBus.Send(command);
  6. }

UnitTest

结果(倒序是正常的,总的来说,事件链的执行会是一个深度优先的调用):

审计(以及Session)

在上个测试例子中,使用了控制台输出来表明各个事件的执行顺序。在正常开发过程中,我们总不能到处输出到控制台,就算输出了也不见得有用。所以我们使用另外的方法来实现这一点。博主第一次接触审计这个概念是在接触一个名为ABP的框架的时候【此处应有链接】,里面包含了很多的信息(用户身份,性能计数,时间等...),当时非常震惊。于是就把ABP源码中的一个文件夹扒了下来自己用。ABP实现了Service方法的审计,用的是动态代理(Castle)。而Cqrs本身就留了无数的注入点,所以实现起来更加直观和方便。另外,博主同时参考了ABP的Session的实现,然后搞出一个土鳖版的Session。虽然比较无耻,但是对于以后ABP的理解应该会有帮助。

博主当初匆匆忙忙的撸了一个CQRS的框架,只是想试水,所以没有想很多,Auditing部分真的是直接扒下来改了改,现在开始写博客,有更多的时间思考,所以打算从头开始实现一个。首先开始抽象,Auditing是一种消息,同时还要考虑它的存放问题,所以定义了以下两个接口(由于Abudting还包含了其他方面,如web,所有接口定义移到Dpfb层):

  1. public interface IAuditInfo
  2. {
  3.  
  4. }
  5.  
  6. public interface IAuditStorage
  7. {
  8. IEnumerable<IAuditInfo> Retrive();
  9. }

Auditing接口

顺便实现一个基于内存的存储:

  1. public class MemoryAuditStorage:IAuditStorage
  2. {
  3. private List<IAuditInfo> _inMemory = new List<IAuditInfo>();
  4.  
  5. IEnumerable<IAuditInfo> IAuditStorage.Retrive()
  6. {
  7. return _inMemory;
  8. }
  9.  
  10. void IAuditStorage.Save(IAuditInfo auditInfo)
  11. {
  12. _inMemory.Add(auditInfo);
  13. }
  14. }

MemoryAuditStorage

接下来实现Cqrs的命令和时间的审计对象。由于事件的调用链是棵树,这里在Dpfb层引入一些必须的数据结构(见DaaStructure名称空间)。以下是针对Cqrs的AuditInfo实现:

  1. public class CommandEventAuditInfo : ExtendedTreeNode<CommandEventAuditInfo>, IAuditInfo
  2. {
  3. public DateTime InvokedTime { get; set; }
  4.  
  5. /// <summary>
  6. /// 单位为毫秒
  7. /// </summary>
  8. public int InvokingDuration { get; set; }
  9.  
  10. public IDpfbSession DpfbSession { get; set; }
  11.  
  12. public Type CommandEventHandlerType { get; set; }
  13.  
  14. public Type CommandEventType { get; set; }
  15.  
  16. public Type DeclaredHandlerType { get; set; }
  17.  
  18. //todo 待扩展
  19. public object ThreadInfo { get; set; }
  20.  
  21. public bool Stopped { get; private set; }
  22.  
  23. private Stopwatch _stopwatch = new Stopwatch();
  24.  
  25. public void Start()
  26. {
  27. _stopwatch.Start();
  28. }
  29.  
  30. public void Stop()
  31. {
  32. _stopwatch.Stop();
  33. InvokingDuration = (int) _stopwatch.ElapsedMilliseconds;
  34. Stopped = true;
  35. Current = Parent;
  36. }
  37.  
  38. public static ConcurrentDictionary<int, CommandEventAuditInfo> ConcurrentDic =
  39. new ConcurrentDictionary<int, CommandEventAuditInfo>();
  40.  
  41. public override string ToString()
  42. {
  43. return PrintTree();
  44. }
  45.  
  46. public string PrintTree()
  47. {
  48. var sb = new StringBuilder();
  49. var userStr = DpfbSession != null ? DpfbSession.ToString() : "匿名用户";
  50. var @abstract = string.Format("命令事件调用分析[{3}]:用户[{1}]引发了[{0}],总耗时[{2}]毫秒。调用链:",
  51. CommandEventType.Name, userStr, InvokingDuration, InvokedTime);
  52. sb.Append(@abstract);
  53. var recursionDepth = ;
  54. return PrintTree(this, ref recursionDepth, sb);
  55. }
  56.  
  57. private string PrintTree(CommandEventAuditInfo auditInfo, ref int recursionDepth, StringBuilder sb)
  58. {
  59. sb.AppendLine();
  60. var span = recursionDepth ==
  61. ? ""
  62. : string.Join("", new string[recursionDepth].Select(i => " ").ToArray());
  63. sb.Append(span + "|---");
  64. sb.AppendFormat("[{2}]处理[{0}],耗时[{1}毫秒]", auditInfo.CommandEventType.Name,
  65. auditInfo.InvokingDuration, auditInfo.CommandEventType.Name);
  66. if (auditInfo.Children.Any())
  67. recursionDepth++;
  68. foreach (var commandEventAuditInfo in auditInfo.Children)
  69. {
  70. PrintTree(commandEventAuditInfo, ref recursionDepth, sb);
  71. }
  72. return sb.ToString();
  73. }
  74.  
  75. private static CommandEventAuditInfo CreateUnstoppedOnCurrentThread()
  76. {
  77. Func<int, CommandEventAuditInfo> creator = k => new CommandEventAuditInfo()
  78. {
  79. ThreadInfo = k
  80. };
  81. var threadName = Thread.CurrentThread.ManagedThreadId;
  82. var auditInfo = ConcurrentDic.GetOrAdd(threadName, creator);
  83. if (auditInfo.Stopped)
  84. {
  85. return ConcurrentDic.AddOrUpdate(threadName, creator, (k, nv) => creator((int) nv.ThreadInfo));
  86. }
  87. return auditInfo;
  88. }
  89.  
  90. public static CommandEventAuditInfo StartNewForCommand<TCommand>(Type handlerType) where TCommand : ICommand
  91. {
  92. var root = CreateUnstoppedOnCurrentThread();
  93. root.InvokedTime = DateTime.Now;
  94. root.CommandEventHandlerType = handlerType;
  95. root.CommandEventType = typeof (TCommand);
  96. root.DeclaredHandlerType = typeof (ICommandHandler<TCommand>);
  97. Current = root;
  98. return root;
  99. }
  100.  
  101. public static CommandEventAuditInfo StartNewForEvent<TEvent>(Type handlerType) where TEvent : IEvent
  102. {
  103. var auditInfo = new CommandEventAuditInfo()
  104. {
  105. CommandEventType = typeof (TEvent),
  106. CommandEventHandlerType = handlerType,
  107. DeclaredHandlerType = typeof (IEventHandler<TEvent>),
  108. InvokedTime = DateTime.Now
  109. };
  110. Current.Children.Add(auditInfo);
  111. auditInfo.Parent = Current;
  112. return auditInfo;
  113. }
  114.  
  115. public static CommandEventAuditInfo Root
  116. {
  117. get { return CreateUnstoppedOnCurrentThread(); }
  118. }
  119.  
  120. public static CommandEventAuditInfo Current { get; private set; }
  121. }

AuditInfo4Cqrs

稍作修改之后,重新运行单元测试:

  1. [TestMethod]
  2. public void TestAuditing()
  3. {
  4. var command = new TestCommand {Message = "国庆记得回家"};
  5. Configuration.CommandBus.Send(command);
  6. command = new TestCommand {Message = "国庆记得回家"};
  7. Configuration.CommandBus.Send(command);
  8. foreach (var auditInfo in Configuration.AuditStorage.Retrive())
  9. {
  10. Console.WriteLine(auditInfo.ToString());
  11. }
  12. }

TestAuditing

这里是运行结果[事件调用链的关系有误,请移步第二篇查看]:

至于Session,此时实现并不能表示其作用,所以只是定义了一个接口。

补丁

至此,一个可以发挥作用的Cqrs就完成了。同时也遗留下一些需要思考的问题:

【事件的继承关系】

在博主的目前实现中,是不考虑事件的继承关系的。

【事件的先后顺序】

使用Handler实现方法上的Attribute实现,这个仅仅作为辅助功能实现。逻辑上的先后顺序由事件链指定。

【CqrsAuditng的生命周期】

目前博主使用Unity作IoC,基于线程的生命周期管理,会存在并发问题(ASP.NET),需要继续考虑。

【Searchers的性能优化】

实际上调用链在编译期间就确定了,所以,可以使用ExpressionTree作缓存。

【事件/命令异步执行的支持】

在ASP.NET中,使用基于TPL的一套异步编程框架可以提高吞吐。这个可以大致这么理解:在web系统中,存在两种角色,请求入口(IIS)【标记为A】,以及请求处理者(实现代码)【标记为B】。我们先假定整个系统只有一个A。那么,在改进之前,情况是“A接到了一个请求告诉B,然后看着B把事情干完,再把结果告诉请求方”。改进之后的情况是“A接到了一个请求B,告诉B处理完事情之后给个反馈——A马上开始处理其他请求。”显然,在改进之前A的等待时间很长,改进之后A当了甩手掌柜(只等报告),等待时间很短。于是乎,A单位时间能的处理量就上去了,吞吐就上去了。然而,对于一次请求而言,时长取决于B,除非B的工作效率有所改进,不然并不会提升性能。

这些是博主的个人理解,并不求多少准确,仅希望能大致描述这么一个意思。

然而博主做了很多测试,并没有反应出这一点。详细内容,请移步这篇文章:【此处应有链接】

【Audting的配置】

这是ABP做的好的地方,也是工作量最大的地方,配置的优先级等。

此篇完成时,所使用的代码:【http://pan.baidu.com/s/1o6IeNXK

CQRS学习——最小单元的Cqrs(CommandEvent)[其一]的更多相关文章

  1. CQRS学习——Dpfb以及其他[引]

    [Dpfb的起名源自:Ddd Project For Beginer,这个Beginer自然就是博主我自己了.请大家在知晓这是一个入门项目的事实上,怀着对入门者表示理解的心情阅读本系列.不胜感激.] ...

  2. .net架构设计读书笔记--第三章 第10节 命令职责分离(CQRS)简介(Introducing CQRS)

    一.分离查询命令 Separating commands from queries     早期的面向DDD设计方法的难点是如何设计一个类,这个类要包含域的方方面面.通常来说,任务软件系统方法调用可以 ...

  3. CQRS+ES项目解析-Diary.CQRS

    在<当我们在讨论CQRS时,我们在讨论些神马>中,我们讨论了当使用CQRS的过程中,需要关心的一些问题.其中与CQRS关联最为紧密的模式莫过于Event Sourcing了,CQRS与ES ...

  4. 【架构】从instagram学习最小化IT是怎么做的

    Keep it very simple (极简主义) Don't re-invent the wheel (不重复发明轮子) Go with proven and solid technologies ...

  5. CQRS学习——IOC,配置,仓储隔离以及QueryEntry[其三]

    从IoC开始说起 博主最早开始用的IoC容器叫AutoFac,那时候用它主要是为了生命周期管理——将EF上下文的生命周期限定为每请求.当然也总是每每听到IoC的好处,但是仍然不能理解其优势.最近在学习 ...

  6. CQRS学习——Cqrs补丁,async实验以及实现[其二]

    实验——async什么时候提高吞吐 async是一个语法糖,用来简化异步编程,主要是让异步编程在书写上接近于同步编程.总的来收,在await的时候,相当于附加上了一个.ContinueWith(). ...

  7. CQRS学习——一个例子(其六)

    [先上链接:http://pan.baidu.com/s/1o62AHbc ] 多图杀猫 先用一组图看看实现的功能: 添加一个功能 假定现在要添加一个书本录入的功能,那么执行如下的操作: 1.添加Co ...

  8. CQRS学习——集成ASP.NET Identity[其五]

    [其实和Cqrs没啥关系] 缘由 其实没啥原因,只是觉得以前写了不知多少遍的用户登录复用性太差,实现的功能也不多. 依赖的Nuget包 简单登陆 就简单登陆而言,只需要实现如下接口/抽象类: Stor ...

  9. CQRS学习——Storage实现(EF+Code First+DynamicReponsitory)[其四]

    [这里是的实现,指的是针对各个数据访问框架的一个基础实现] 目标 定义仓储/QueryEntry的基本功能 实现仓储的基本功能,以利于复用 实现一些常用的功能 提供一些便利的功能 目标框架 博主使用的 ...

随机推荐

  1. IT技术开发人员35岁之前应该做的十件事

    第一,学会本行业所需要的一切知识并有所发展.已故零件大王布鲁丹在他35岁时,已经成为零件行业的领袖,并且组建了年收入达千万美元的海湾与西部工业公司.每个人在年轻时都可能有过彻夜不眠.刻苦攻读,这在20 ...

  2. 和阿文一起学H5--设计稿尺寸全攻略

  3. TSQL基础(一) - 查询

    select 1.查询一张表(orders)的所以纪录 select * from Orders 2.查询一张表(orders)某字段的所有记录 select OrderID,OrderDate fr ...

  4. 机器学习 —— 决策树及其集成算法(Bagging、随机森林、Boosting)

    本文为senlie原创,转载请保留此地址:http://www.cnblogs.com/senlie/ 决策树--------------------------------------------- ...

  5. 实例介绍Cocos2d-x中Box2D物理引擎:碰撞检测

    在Box2D中碰撞事件通过实现b2ContactListener类函数实现,b2ContactListener是Box2D提供的抽象类,它的抽象函数:virtual void BeginContact ...

  6. 事件[event]_C#

    事件(event): 1.       事件是类在发生其关注的事情时用来提供通知的方式.例如,封装用户界面控件的类可以定义一个在单击该控件时发生的事件.控件类不关心单击按钮时发生了什么,但它需要告知派 ...

  7. (转)Ubuntu 12.04 LTS 构建高可用分布式 MySQL 集群

    本文的英文版本链接是 http://www.mrxuri.com/index.php/2013/11/20/install-mysql-cluster-on-ubuntu-12-04-lts.html ...

  8. maven安装配置(myeclipse)(一)

    欢迎转载:http://www.cnblogs.com/shizhongtao/p/3374130.html 对于我来说,maven主要用于jar包的管理,避免项目中频繁更换jar的版本,以及网上搜索 ...

  9. poj 2220 Sumsets

                                                                                                     Sum ...

  10. 3月3日[Go_deep]Populating Next Right Pointers in Each Node

    原题:Populating Next Right Pointers in Each Node 简单的链表二叉树增加Next节点信息,没什么坑.不过还是WA了两次,还是有点菜,继续做,另外leetcod ...