首页 > 代码库 > 大话CQRS-简单

大话CQRS-简单

前言:

看了最新的《老友记》学到了一句“装逼格”的句子,真正优秀的事物包含了:简单,美好,普适。

最近在研究CQRS,争取用到项目中,当然为了完善,体悟CQRS项目也是在一步步的完善中。

舒畅感

第一篇说的是简单,当然简单并不是指CQRS本身的概念,而是仅仅作为一个初学者在尝试搭架子的过程当中突然接触到CQ的想法时那种醍醐灌顶的舒畅感。

唐僧:喂喂喂!大家不要生气,生气会犯了嗔戒的!悟空你也太调皮

了,我跟你说过叫你不要乱扔东西,你怎么又…你看我还没说

完你又把棍子给扔掉了!月光宝盒是宝物,你把他扔掉会污染

环境,要是砸到小朋友怎么办?就算砸不到小朋友砸到那些花

花草草也是不对的!

......

悟空:所以呢我就抓住苍蝇挤破它的肚皮把它的肠子扯出来再用它的

肠子勒住他的脖子用力一拉,呵--!整条舌头都伸出来啦!

我再手起刀落哗--!整个世界清净了。现在大家明白,为什

么我要杀他!

 

当我们把太多的时间用在研究概念上时,妄想通过读二手文章揣摩大牛心态去了解CQRS思想时就是遇到唐僧的赶脚,要死啊!

但是我们先以CQ的思想去尝试一个简单而又普适的小项目时,整个世界清静了。

 

贱解

以“贱贱”的心态说一下我对于CQRS的理解。

上面是一张比较经典的CQRS架构图,对于Q端我感觉我理解了,问题主要是集中在C端。
 
按照我的理解:
 
1.C端是将现实中的业务封装成对应的Command,在通过“外观模式”交由对应的CommandHandler去处理;
 
2.由于现实中的Command往往是由多个Domain组成,所以在对应的CommandHandler中对于Command的处理是交由对应的DomainModel去处理的,再下面就是Repository了,这里属于经典的DDD;
 
那么问题来了:
 
1.按照概念的描述每一种Command的最终行为是交由一系列的Event去真正实现的,所以每次Command产生的Event会被持久起来EventSource (有的建议是以关系数据库或者InMemory,我喜欢InMemory)。
 
2.由于庞大的系统对于命令的处理往往是以异步方式实现的,所以最终产生的EventSource 会异步到ReadDB中,达到数据一致性的目的(当然这里实现方式有很多比如ReadDB本身是NoSql,或者通过主从方式实现等)。
 
我的问题:
 
1.对于建立EventSource 的目的是否真的有必要?其真正的应用场景是什么?
由于概念上EventSource 可以持久整个Event的生命周期,并且可以回到任意的一个Event的状态上,如果EventSource 较为庞大的话可以通过建立“快照”进行优化,但是现实场景我们是否真的需要考虑这么多,其真实处理又是如何?
 
2.持久了Event之后,想回滚到对应的某一个Event的状态可以,但是否可以回滚一些Command他的回路又是在哪里?还是会产生一个回滚Command再次产生Event追加到EventSource ,那样回滚某一状态Event的目的就没有了,希望能有参考意见。
 
3.如何控制Event的并发?
比如业务上产生并发Command那么最终会作用在产生的Event上,我的了解解决并发需要排队处理,所以我想放在Event上处理更加合适(方式按具体业务),这样是否合适?或者你有哪些更好的方法?

上面是包括了理解与问题,问题是为了之后的文章或者园友能够帮忙解答一下下,回归正题先说理解吧。

了解CQRS需要对里面的关键词做了解。

C:

Command:每一种行为除Q以外的都可以看做Command;

Command Bus: 命令总线,所有的Command将会被发送到CommandBus中;

Command Handler:每一种Command最后会有对应的CommandHandler进行处理;

Domain:由于Command最终需要落实到业务Model上做操作,所以Domain是真正去处理业务Command的地方;

Event:Domain中对于业务Command最终会分解成多种对于聚合根的Event;

Repository:在Domain分发处理之后持久Event;

EventSource: 被持久的Event集合,可以InMemory也可以放在关系DB中;

Event Bus:类似CommandBus去同步(或异步)的方式装载Event的最后状态;

Event Handler:类似于CommandHandler单独对Event最处理;

Read DB:最终修改DB状态,达到数据一致的目的,当然ReadDB可以以NoSql的方式对Q端进行优化;

Message: 暂时不太明白;

 

Q:

Q端感觉没什么可以说的,就是建立一套方便业务Query与ReportDB之间的管道,可以专门对于Query进行优化而不影响整个的业务逻辑(C端那些行为)。从而达到了在业务,代码结构上的读写分离。

没有代码就是耍流氓

最后奉上一段小Demo作为CQRS进化的初级版,后续对于CQRS的理解及应用的进化也会发表上来。

结构图:

是一段Web程序:

QueryProcess:负责处理Q端,下层以T-SQL,EF去实现;

DataProcess:负责C端,下层以EF实现;

Model:包含了CommandModel,ViewModel,DTOModel;

Data:包含EF及Repository;

Common:一些基础通用的Helper类。

API:是和另外两个系统以Restful做交互的;

 

Controller:

        [HttpGet]        [UserAuthentication]        public ActionResult Index(string id)        {            AccountViewModel vm = new AccountViewModel();            vm.roleList = ServiceLoader.RoleQueryProcess.FetchAll(null).ToList();            vm.accountDtoList = ServiceLoader.AccountQueryProcess.FetchAllToDto().ToList();            if (!string.IsNullOrEmpty(id)) { vm.tag = id; }            return View(vm);        }

通过外观模式就(ServiceLoader)获取对应的Query;

QueryProcess:

            string sql = string.Format(@"select top {0}                    ID                    ,RoleName                    ,RoleCode                    ,ParentId from                    Tb_Role a                     where 1=1 {1} and id not in                     (select top {2} id from Tb_Role where 1=1 {1} order by id desc)                     order by id desc"                , PageConfig.ShowCount                , ""                , PageConfig.ShowCount * (PageConfig.RolePageNow - 1));            DataTable queryTable = SqlHelper.GetTableText(sql, null)[0];            string json = JsonConvert.SerializeObject(queryTable);            return JsonConvert.DeserializeObject<List<AutodeskTool.Data.Tb_Role>>(json);

通过T-Sql方式进行DB的Query,当然中间可以嫁接上Cache;

Command:

    public class RegisterCommand:BaseCommand    {        public string userName { get; set; }        public string password { get; set; }        public bool isRemember { get; set; }    }

一个简单的Command产生;

        [HttpPost]        public ActionResult Login(SignInCommand sign)        {            Tb_Account account = new Tb_Account()            {                UserName = sign.userName,                Password = sign.password            };            CommandActionResult commentResult = ServiceFactory.AccountService.AccountLogin(account);            if (commentResult.isOk)            {              //...            }         }        

将Command同样通过外观模式的ServiceFactory通过反射分发到真的Handler程序(AccountService);

Domain:

       public CommandActionResult AccountLogin(Tb_Account account)        {            try            {                Tb_Account result = AccountRepository.FetchT(a => a.UserName.Equals(account.UserName) && a.Password.Equals(account.Password));}cache{//...}}

由于还不太清楚EventSource的真正合理的用法这里我就直接将Command的聚合根持久到DB了;

 

由于还是对于CQRS的使用与理解上还处于初级阶段,但是当接触这一概念是还是有种很舒服的感觉,不完善的地方还请大家包含,也希望大牛能够指点,后续还会带来。

thanks.

大话CQRS-简单