asp.net core系列 62 CQRS架構(gòu)下Equinox開(kāi)源項(xiàng)目分析

一.DDD分層架構(gòu)介紹

本篇分析CQRS架構(gòu)下的Equinox開(kāi)源項(xiàng)目万细。該項(xiàng)目在github上star占有2.4k。便決定分析Equinox項(xiàng)目來(lái)學(xué)習(xí)下CQRS架構(gòu)瓦戚。再講CQRS架構(gòu)時(shí)匪燕,先簡(jiǎn)述下DDD風(fēng)格,在DDD分層架構(gòu)中侮叮,一般包含表現(xiàn)層避矢、應(yīng)用程序?qū)?應(yīng)用服務(wù)層)、領(lǐng)域?qū)?領(lǐng)域服務(wù)層)囊榜、基礎(chǔ)設(shè)施層审胸。在DDD中講到服務(wù)這個(gè)術(shù)語(yǔ)時(shí),比如領(lǐng)域服務(wù),應(yīng)用層服務(wù)等卸勺,這個(gè)服務(wù)是指業(yè)務(wù)邏輯砂沛,而不是指任何技術(shù)如wcf,web服務(wù)。

下圖是從經(jīng)典三層構(gòu)架演變?yōu)镈DD下的分層架構(gòu)圖:



1.表現(xiàn)層

表現(xiàn)層前端往后端post的數(shù)據(jù)稱"輸入模型(InputModel)"曙求,后端控制器傳給前端要顯示的數(shù)據(jù)稱"視圖模型(ViewModel)"碍庵,大多時(shí)候視圖模型與輸入模型是重合的,所在在下面要介紹的開(kāi)源項(xiàng)目中,作者在應(yīng)用服務(wù)層只定義了ViewModels文件夾悟狱。例如在MVC中静浴,控制器里只是編排任務(wù),調(diào)用應(yīng)用程序?qū)蛹方ァT诳刂破髦写a塊應(yīng)該盡可能輕薄马绝,主要作用是找出層與層之間的分離,控制器只是業(yè)務(wù)邏輯占位符挣菲。

在表現(xiàn)層中與運(yùn)行環(huán)境密切相連富稻,表現(xiàn)層需要關(guān)注的是http上下文、會(huì)話狀態(tài)等白胀。



 2. 應(yīng)用服務(wù)層

可以在應(yīng)用服務(wù)層引用領(lǐng)域?qū)雍突A(chǔ)設(shè)施層椭赋,是在領(lǐng)域?qū)又暇幣艠I(yè)務(wù)用例的服務(wù)。該層對(duì)業(yè)務(wù)規(guī)則一無(wú)所知或杠,不會(huì)包含任何與業(yè)務(wù)有關(guān)的狀態(tài)信息哪怔。該層關(guān)鍵特點(diǎn):

(1) 該層是針對(duì)不同的前端。該層與表現(xiàn)層有關(guān),是為表現(xiàn)層服務(wù)。不同的表現(xiàn)層(移動(dòng)认境,webapi, web)都有自己的應(yīng)用服務(wù)層胚委。該層與表現(xiàn)層屬于系統(tǒng)的前端。

(2) 應(yīng)用服務(wù)層可能是有狀態(tài)的叉信,至少就UI任務(wù)進(jìn)度而言亩冬。

(3) 它從表現(xiàn)層獲取輸入模型,然后把視圖模型返回去硼身。
 3. 領(lǐng)域?qū)?/p>

領(lǐng)域?qū)邮亲钪匾妥顝?fù)雜的一層硅急。在DDD的領(lǐng)域模型架構(gòu)下。該層包含了所有針對(duì)一個(gè)或多個(gè)用例業(yè)務(wù)邏輯佳遂,領(lǐng)域?qū)影粋€(gè)領(lǐng)域模型和一組可能的服務(wù)营袜。

領(lǐng)域模型大多時(shí)候是一個(gè)實(shí)體關(guān)系模型,可以由方法組成丑罪。是擁有數(shù)據(jù)和行為荚板。如果缺少重要行為,那就是一個(gè)數(shù)據(jù)結(jié)構(gòu)吩屹,稱為貧血模型跪另。領(lǐng)域模型是實(shí)現(xiàn)統(tǒng)一語(yǔ)言和表達(dá)業(yè)務(wù)流程所需的操作。

領(lǐng)域?qū)影姆?wù)是領(lǐng)域服務(wù)祟峦,是涉及多個(gè)領(lǐng)域模型而無(wú)法放個(gè)單個(gè)領(lǐng)域模型中的領(lǐng)域邏輯罚斗。領(lǐng)域服務(wù)是一個(gè)類徙鱼,包含了多個(gè)領(lǐng)域模型實(shí)體的行為宅楞。領(lǐng)域服務(wù)通常也需要訪問(wèn)基礎(chǔ)設(shè)施層。

在DDD的CQRS架構(gòu)下袱吆,使用二個(gè)不同的領(lǐng)域?qū)友嵫茫皇且粋€(gè)(在Equinox項(xiàng)目中混合成一個(gè))。這種分離把查詢操作放在一層(查詢領(lǐng)域?qū)?绞绒,把命令操作放在另一層(命令領(lǐng)域?qū)?婶希。在CQRS里,查詢棧僅僅基于SQL查詢蓬衡,可以完全沒(méi)有模型喻杈、應(yīng)用程序?qū)雍皖I(lǐng)域?qū)印2樵冾I(lǐng)域?qū)又恍枰氀P皖怐TO來(lái)做傳輸對(duì)象狰晚。

  1. 基礎(chǔ)設(shè)施層

這層使用具體技術(shù)有關(guān)的任何東西:O/RM工具的數(shù)據(jù)訪問(wèn)持久層筒饰、IOC容器的實(shí)現(xiàn)(Unity)、以及很多其它橫切關(guān)注點(diǎn)的實(shí)現(xiàn),如安全(Oauth2)壁晒、日志記錄瓷们、跟蹤、緩存等。最突出的組件是持久層谬晕。

二.CQRS概述

1.簡(jiǎn)介

CQRS是DDD開(kāi)發(fā)風(fēng)格下對(duì)領(lǐng)域模型架構(gòu)的一種簡(jiǎn)化改進(jìn)碘裕。任何業(yè)務(wù)系統(tǒng)基本都是查詢與寫入,對(duì)應(yīng)CQRS是指命令/查詢責(zé)任分離攒钳,查詢不以任何方式修改系統(tǒng)狀態(tài)帮孔,只返回?cái)?shù)據(jù)。另一方面夕玩,命令(寫入)則修改系統(tǒng)的的狀態(tài)你弦,但不返回?cái)?shù)據(jù),除了狀態(tài)代碼或確認(rèn)信息燎孟。在CQRS里禽作,查詢棧僅基于sql查詢,可以完全沒(méi)有模型揩页,應(yīng)用程序?qū)雍皖I(lǐng)域?qū)涌醭ァQRS方案還可以為命令棧和查詢棧準(zhǔn)備不同的數(shù)據(jù)庫(kù)(讀與寫)。

2.CQRS的好處

(1)是簡(jiǎn)化設(shè)計(jì)降低復(fù)雜性爆侣,對(duì)于查詢來(lái)說(shuō)萍程,可以直接讀取基礎(chǔ)設(shè)施層的倉(cāng)儲(chǔ)。

(2)是增強(qiáng)可伸縮性的潛能兔仰。比如讀取是主導(dǎo)操作茫负,可以引入某種程序的緩存,極大減少訪問(wèn)數(shù)據(jù)庫(kù)的次數(shù)乎赴。比如寫入在高峰期減慢系統(tǒng)忍法,可以考慮從經(jīng)典的同步寫入模型換到異步寫入甚至命令隊(duì)列。分離了查詢和命令榕吼,可以完全隔離處理這兩個(gè)部分的可伸縮性饿序。

3.CQRS實(shí)現(xiàn)全局圖

在全局圖中,右圖通過(guò)虛線表示雙重分層架構(gòu)羹蚣,分開(kāi)了命令通道和查詢通道原探,每個(gè)通道都有獨(dú)立架構(gòu)。在命令通道里顽素,任何來(lái)自表現(xiàn)層的請(qǐng)求都會(huì)變成一個(gè)命令咽弦,并加入到處理器隊(duì)列。每個(gè)命令都攜帶信息胁出。每個(gè)命令都是一個(gè)邏輯單元型型,可以充分地驗(yàn)證相關(guān)對(duì)象的狀態(tài),智能的決定執(zhí)行哪些更新以及拒絕哪些更新划鸽。處理命令可能會(huì)產(chǎn)生事件(事件通常是記錄命令發(fā)生的事情)输莺,這些事件會(huì)被其它注冊(cè)組件處理戚哎。


三. Equinox開(kāi)源項(xiàng)目總覽

1.準(zhǔn)備環(huán)境

(1) Github開(kāi)源地址下載。Full ASP.NET Core 2.2 application with DDD, CQRS and Event Sourcing

(2) 在sqlserver里執(zhí)行sql文件GenerateDataBase.sql嫂用。

(3) 修改appsettings.json中的ConnectionStrings的數(shù)據(jù)庫(kù)連接地址型凳。



 2.項(xiàng)目分層說(shuō)明

               表現(xiàn)層:Equinox.UI.Web、Equinox.Services.Api

               應(yīng)用服務(wù)層: Equinox.Application

               領(lǐng)域?qū)? Equinox.Domain嘱函、Equinox.Domain.Core

               基礎(chǔ)設(shè)施層: Equinox.Infra.Data(EF持久化)

               基礎(chǔ)設(shè)施層下的橫切關(guān)注點(diǎn):

                 Equinox.Infra.CrossCutting.Bus(事件和命令總線)

                 Equinox.Infra.CrossCutting.Identity(用戶管理如登錄甘畅、注冊(cè)、授權(quán))

                 Equinox.Infra.CrossCutting.IoC(控制反轉(zhuǎn)的服務(wù)注入)
  1. 項(xiàng)目架構(gòu)流程梳理圖

    流程圖更正:領(lǐng)域?qū)覧quinox.Domain不需要 引用 基礎(chǔ)設(shè)施層事件總線Equinox.Infra.CrossCutting.Bus往弓。在DDD風(fēng)格下領(lǐng)域?qū)邮仟?dú)立的疏唾,原則上不依賴于其它層。

四.表現(xiàn)層分析

在表現(xiàn)層是Equinox.UI.Web和Equinox.Services.Api 服務(wù)函似。在Equinox.UI.Web下主要是用控制器中的CustomerController來(lái)演示CQRS框架的實(shí)現(xiàn)槐脏,以及AccountController和ManageController的用戶登錄、注冊(cè)撇寞、退出和用戶信息管理顿天。

對(duì)于AccountController和ManageController兩個(gè)控制器關(guān)聯(lián)著Equinox.Infra.CrossCutting.Identity項(xiàng)目。Identity項(xiàng)目包括了需要用的視圖模型蔑担、對(duì)系統(tǒng)的授權(quán)牌废、自定義用戶表數(shù)據(jù)、用戶數(shù)據(jù)同步到數(shù)據(jù)庫(kù)的遷移版本管理啤握、郵件和SMS鸟缕。對(duì)于授權(quán)方案通過(guò)Equinox.Infra.CrossCutting.IoC來(lái)注入服務(wù)。如下所示:

 // ASP.NET Authorization Polices
           services.AddSingleton<IAuthorizationHandler, ClaimsRequirementHandler>();

Equinox.Services.Api項(xiàng)目實(shí)現(xiàn)的功能與Web站點(diǎn)差不多排抬,是通過(guò)暴露Web API來(lái)實(shí)現(xiàn)懂从。下面是表現(xiàn)層的二個(gè)項(xiàng)目:


五. 應(yīng)用服務(wù)層分析

Equinox.Application應(yīng)用服務(wù)層包括對(duì)AutoMapper的配置管理,通過(guò)AutoMapper實(shí)現(xiàn)視圖模型和領(lǐng)域模型的實(shí)體互轉(zhuǎn)畜埋。定義ICustomerAppService服務(wù)接口供表現(xiàn)層調(diào)用莫绣,由CustomerAppService類來(lái)實(shí)現(xiàn)該接口畴蒲。項(xiàng)目包含了Customer需要的視圖模型悠鞍。還有事件源EventSource。

由CustomerAppService類來(lái)實(shí)現(xiàn)表現(xiàn)層的查詢模燥、命令咖祭、獲取事件源。項(xiàng)目結(jié)構(gòu)如下:


六.領(lǐng)域?qū)覦omain.Core分析

領(lǐng)域?qū)邮琼?xiàng)目分層架構(gòu)中蔫骂,最重要的一層么翰,也是相對(duì)復(fù)雜的一層。該層作者用了二個(gè)項(xiàng)目包括:Domain.Core和Domain項(xiàng)目結(jié)構(gòu)如下所示:



對(duì)于Domain.Core項(xiàng)目主要是定義命令和事件的基類辽旋。源頭是定義的抽象類Message浩嫌。對(duì)于命令和事件,任何前端都會(huì)發(fā)送消息給應(yīng)用程序?qū)? Message消息就是數(shù)據(jù)傳輸對(duì)象檐迟,通常消息定義為一個(gè)Message基類開(kāi)始,作為數(shù)據(jù)容器码耐。

這里使用MediatR中間件作為命令和事件的實(shí)現(xiàn)追迟。MediatR支持兩種消息類型:Request/Response和Notification。先看下Message消息基類定義:

  //注入服務(wù)
    services.AddMediatR(typeof(Startup));
/// <summary>
    /// Message消息 
    /// 放入通用屬性,甚至是普通標(biāo)記骚腥,沒(méi)有屬性
    /// </summary>
    public abstract class Message : IRequest<bool>
    {
        /// <summary>
        /// 消息類型:實(shí)現(xiàn)Message的命令或事件類型
        /// </summary>
        public string MessageType { get; protected set; }

        /// <summary>
        /// 聚合ID
        /// </summary>
        public Guid AggregateId { get; protected set; }

        protected Message()
        {
            MessageType = GetType().Name;
        }
    }

消息有二種:命令和事件敦间。兩種消息都包含了數(shù)據(jù)傳輸對(duì)象。命令和事件有些微妙差別束铭,命令和事件都是Message派生類廓块。

/// <summary>
    /// Event 領(lǐng)域消息
    /// 事件類是不可變的,它表示已經(jīng)發(fā)生的事情契沫,意味著只有私有set,沒(méi)有寫入方法带猴。
    /// 事件存放通用屬性,例如事件觸發(fā)時(shí)間懈万,觸發(fā)的用戶浓利,數(shù)據(jù)版本號(hào)。
    /// </summary>
    public abstract class Event : Message, INotification
    {
        public DateTime Timestamp { get; private set; }

        protected Event()
        {
            //事件時(shí)間
            Timestamp = DateTime.Now;
        }
    }
/// <summary>
    /// Command領(lǐng)域命令(增刪改)钞速,不返回任何結(jié)果(void)贷掖,但會(huì)改變數(shù)據(jù)對(duì)象的狀態(tài)。
    /// </summary>
    public abstract class Command : Message
    {
        public DateTime Timestamp { get; private set; }

        //DTO綁定驗(yàn)證渴语,使用Fluent API來(lái)實(shí)現(xiàn)
        public ValidationResult ValidationResult { get; set; }

        protected Command()
        {
            //命令時(shí)間
            Timestamp = DateTime.Now;
        }

        //實(shí)現(xiàn)Command抽象類的DTO數(shù)據(jù)驗(yàn)證
        public abstract bool IsValid();
    }

Domain.Core項(xiàng)目還定義了領(lǐng)域?qū)嶓w和領(lǐng)域值對(duì)象的基類實(shí)現(xiàn)苹威。例如:在領(lǐng)域?qū)嶓w基類中實(shí)現(xiàn)了相等性、運(yùn)算符重載驾凶、重寫HashCode牙甫。對(duì)于實(shí)體和值對(duì)象主要區(qū)別是:實(shí)體有明確的身份標(biāo)識(shí)如主鍵ID,GUID调违。

 public abstract class Entity
      public abstract class ValueObject<T> where T : ValueObject<T>

Domain.Core項(xiàng)目中的Notifications消息文件夾窟哺,用來(lái)確認(rèn)消息發(fā)送后的處理狀態(tài)。下面是表現(xiàn)層發(fā)送更新命令后技肩,IsValidOperation()確認(rèn)消息處理的狀態(tài)情況且轨。

[HttpPost]
        [Authorize(Policy = "CanWriteCustomerData")]
        [Route("customer-management/edit-customer/{id:guid}")]
        [ValidateAntiForgeryToken]
        public IActionResult Edit(CustomerViewModel customerViewModel)
        {
            if (!ModelState.IsValid) return View(customerViewModel);

            _customerAppService.Update(customerViewModel);

            if (IsValidOperation())
                ViewBag.Sucesso = "Customer Updated!";

            return View(customerViewModel);
        }

Domain.Core項(xiàng)目中的Bus文件夾,用來(lái)做命令總線和事件總線的發(fā)送接口虚婿,由Equinox.Infra.CrossCutting.Bus項(xiàng)目來(lái)實(shí)現(xiàn)總線接口的發(fā)送旋奢。

七.領(lǐng)域?qū)覦omain分析

下面是Domain項(xiàng)目結(jié)構(gòu)如下:



在上面結(jié)構(gòu)中,Commands和Events文件夾分別用來(lái)存儲(chǔ)命令和事件的數(shù)據(jù)傳輸對(duì)象然痊,是貧血的DTO類至朗,也可以理解為領(lǐng)域?qū)嶓w。例如Commands文件夾下命令數(shù)據(jù)傳輸對(duì)象定義:

   /// <summary>
    /// Customer數(shù)據(jù)轉(zhuǎn)輸對(duì)象抽象類剧浸,放Customer通過(guò)屬性 /// </summary>
    public abstract class CustomerCommand : Command
    { public Guid Id { get; protected set; } public string Name { get; protected set; } public string Email { get; protected set; } public DateTime BirthDate { get; protected set; }
    }
 /// <summary>
    /// Customer注冊(cè)命令消息參數(shù)
    /// </summary>
    public class RegisterNewCustomerCommand : CustomerCommand
    {
        public RegisterNewCustomerCommand(string name, string email, DateTime birthDate)
        {
            Name = name;
            Email = email;
            BirthDate = birthDate;
        }

           /// <summary>
        /// 命令信息參數(shù)驗(yàn)證
        /// </summary>
        /// <returns></returns>
        public override bool IsValid()
        {
            ValidationResult = new RegisterNewCustomerCommandValidation().Validate(this);
            return ValidationResult.IsValid;
        }
    }

當(dāng)在應(yīng)用服務(wù)層發(fā)送命令(Bus.SendCommand)后锹引,由領(lǐng)域?qū)拥腃ommandHandlers文件夾下的類來(lái)處理命令矗钟,再調(diào)用EF持久層來(lái)改變實(shí)體狀態(tài)。下面梳理下命令的執(zhí)行流程嫌变,由表現(xiàn)層開(kāi)始一個(gè)customer新增如下所示:


image

當(dāng)在表現(xiàn)層點(diǎn)擊Create后真仲,調(diào)用應(yīng)用服務(wù)層Register方法,觸發(fā)一個(gè)新增事件初澎,代碼如下:

/// <summary>
        /// 新增
        /// </summary>
        /// <param name="customerViewModel">視圖模型</param>
        public void Register(CustomerViewModel customerViewModel)
        {
            //將視圖模型 映射到  RegisterNewCustomerCommand 新增命令實(shí)體
            var registerCommand = _mapper.Map<RegisterNewCustomerCommand>(customerViewModel);
            Bus.SendCommand(registerCommand);
        }

當(dāng)SendCommand發(fā)送命令后秸应,由領(lǐng)域?qū)覥ustomerCommandHandler類中的Handle來(lái)處理該命令,如下所示:

/// <summary>
        /// Customer注冊(cè)命令處理
        /// </summary>
        /// <param name="message"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task<bool> Handle(RegisterNewCustomerCommand message, CancellationToken cancellationToken)
        {
            //對(duì)實(shí)體屬性進(jìn)行驗(yàn)證
            if (!message.IsValid())
            {
                NotifyValidationErrors(message);
                return Task.FromResult(false);
            }

            //將命令消息轉(zhuǎn)成領(lǐng)域?qū)嶓w
            var customer = new Customer(Guid.NewGuid(), message.Name, message.Email, message.BirthDate);

            //如果注冊(cè)用戶郵件已存在,發(fā)起一個(gè)事件
            if (_customerRepository.GetByEmail(customer.Email) != null)
            {
                Bus.RaiseEvent(new DomainNotification(message.MessageType, "The customer e-mail has already been taken."));
                return Task.FromResult(false);
            }

            //由Equinox.Infra.Data.Repository來(lái)實(shí)現(xiàn)數(shù)據(jù)持久化碑宴。事件是過(guò)去在系統(tǒng)中發(fā)生的事情软啼。該事件通常是命令的結(jié)果.
            _customerRepository.Add(customer);

            //新增成功后,使用事件記錄這次命令延柠。
            if (Commit())
            {
                Bus.RaiseEvent(new CustomerRegisteredEvent(customer.Id, customer.Name, customer.Email, customer.BirthDate));
            }

            return Task.FromResult(true);
        }

下面是注冊(cè)customer的信息祸挪,以及注冊(cè)產(chǎn)生的事件數(shù)據(jù),如下所示:


image

 在領(lǐng)域?qū)拥腎nterfaces文件夾中贞间,最重要的包括IRepository<TEntity>接口贿条,是通過(guò)Equinox.Infra.Data.Repository來(lái)實(shí)現(xiàn)接口,來(lái)進(jìn)行數(shù)據(jù)持久化增热。下面是領(lǐng)域?qū)觽}(cāng)儲(chǔ)接口:

/// <summary>
    /// 領(lǐng)域?qū)觽}(cāng)儲(chǔ)接口整以,定義了通用的方法
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public interface IRepository<TEntity> : IDisposable where TEntity : class
    {
        void Add(TEntity obj);
        TEntity GetById(Guid id);
        IQueryable<TEntity> GetAll();
        void Update(TEntity obj);
        void Remove(Guid id);
        int SaveChanges();
    }
/// <summary>
    /// Customer倉(cāng)儲(chǔ)接口,在基數(shù)倉(cāng)儲(chǔ)上擴(kuò)展
    /// </summary>
    public interface ICustomerRepository : IRepository<Customer>
    {
        Customer GetByEmail(string email);
    }

Interfaces文件夾中還定義了IUser和IUnitOfWork接口類峻仇,也是需要Equinox.Infra.Data.Repository來(lái)實(shí)現(xiàn)公黑。

八. 基礎(chǔ)設(shè)施層分析

Equinox.Infra.Data項(xiàng)目是EF用來(lái)持久化命令和事件,以及查詢數(shù)據(jù)的倉(cāng)儲(chǔ)摄咆,結(jié)構(gòu)如下:


image

 其中UoW文件夾下的UnitOfWork類用來(lái)實(shí)現(xiàn)領(lǐng)域?qū)拥腎UnitOfWork凡蚜,使用Commit保存數(shù)據(jù)。

 public bool Commit()
        {
            return _context.SaveChanges() > 0;
        }

Repository文件夾下的類用來(lái)實(shí)現(xiàn)領(lǐng)域?qū)拥腎Repository接口吭从,使用EF的DbSet來(lái)操作EF TEntity對(duì)象朝蜘,再調(diào)用Commit提交到數(shù)據(jù)庫(kù)。

  public virtual void Add(TEntity obj)
        {
            DbSet.Add(obj);
        }

Repository文件夾下還包含EventSourcing事件源涩金,存儲(chǔ)到StoredEvent表中谱醇。

九.命令總線分析

Equinox.Infra.CrossCutting.Bus項(xiàng)目中使用了中間件MediatR,定義了InMemoryBus類來(lái)實(shí)現(xiàn)領(lǐng)域?qū)拥腎MediatorHandler命令總線接口發(fā)送,使用SendCommand (T)和RaiseEvent (T)方法發(fā)送命令和事件鸭廷。

MediatR是用于消息發(fā)送和消息處理的解耦枣抱,MediatR是一種進(jìn)程內(nèi)消息傳遞機(jī)制熔吗。 支持以同步或異步的形式進(jìn)行請(qǐng)求/響應(yīng)辆床,命令,查詢桅狠,通知和事件的消息傳遞讼载,并通過(guò)C#泛型支持消息的智能調(diào)度轿秧。 其中IRequest和INotification分別對(duì)應(yīng)單播和多播消息的抽象。

例如:在領(lǐng)域?qū)又凶傻蹋琈essage消息實(shí)現(xiàn)IRequest菇篡,代碼如下:

/// <summary>
    /// Message消息 
    /// 放入通用屬性,甚至是普通標(biāo)記,沒(méi)有屬性一喘。IRequest<T> - 有返回值
    /// </summary>
    public abstract class Message : IRequest<bool>

最后Equinox.Infra.CrossCutting.Identity主要做用戶管理驱还,授權(quán),遷移管理凸克。Equinox.Infra.CrossCutting.IoC做整個(gè)解決方案下項(xiàng)目需要的服務(wù)注入议蟆。
參考文獻(xiàn):

Introduction-to-CQRS

Microsoft.NET企業(yè)級(jí)應(yīng)用架構(gòu)設(shè)計(jì) 第二版

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市萎战,隨后出現(xiàn)的幾起案子咐容,更是在濱河造成了極大的恐慌蚂维,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虫啥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡涂籽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門又活,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人柳骄,你說(shuō)我怎么就攤上這事团赏。” “怎么了耐薯?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵舔清,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我曲初,道長(zhǎng)体谒,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任臼婆,我火速辦了婚禮抒痒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颁褂。我一直安慰自己故响,他們只是感情好傀广,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著彩届,像睡著了一般伪冰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上樟蠕,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天贮聂,我揣著相機(jī)與錄音,去河邊找鬼寨辩。 笑死寂汇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捣染。 我是一名探鬼主播骄瓣,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耍攘!你這毒婦竟也來(lái)了榕栏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蕾各,失蹤者是張志新(化名)和其女友劉穎扒磁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體式曲,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年兰伤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钧排。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡符衔,死狀恐怖判族,靈堂內(nèi)的尸體忽然破棺而出项戴,到底是詐尸還是另有隱情,我是刑警寧澤躯枢,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布槐臀,位于F島的核電站氓仲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晰洒。R本人自食惡果不足惜谍珊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一急侥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坏怪,春花似錦、人聲如沸打掘。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至叶撒,卻和暖如春耐版,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背古瓤。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工落君, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皮获。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓洒宝,卻偏偏與公主長(zhǎng)得像雁歌,于是被迫代替她去往敵國(guó)和親知残。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容