ENode 2.0 第一個(gè)真實(shí)案例剖析-一個(gè)簡(jiǎn)易論壇(Forum)
前言
經(jīng)過不斷的堅(jiān)持和努力,ENode 2.0的***個(gè)真實(shí)案例終于出來了。這個(gè)案例是一個(gè)簡(jiǎn)易的論壇,開發(fā)這個(gè)論壇的初衷是為了驗(yàn)證用ENode框架來開發(fā)一個(gè)真實(shí)項(xiàng)目的可行性。目前這個(gè)論壇在UI上是使用了最終一致性,也就是說當(dāng)我們發(fā)帖或回帖后不會(huì)立馬顯示你的帖子或回復(fù)。當(dāng)我們下一次刷新頁(yè)面時(shí),會(huì)顯示出來。這點(diǎn)貌似很多人向我反饋不太習(xí)慣,接受不了,呵呵。我之所以這樣做也是想看看最終一致性大家的接受程度如何,看來UI層面上的最終一致性,大部分人接受不了?;仡^我改進(jìn)下效果,改為立即可以看到帖子或回復(fù)吧!另外,關(guān)于ENode是什么,本文就不多介紹了,可以參考這篇文章的介紹。本文重點(diǎn)介紹一下ENode是如何幫助我們開發(fā)一個(gè)基于DDD+CQRS+Event Sourcing架構(gòu)的應(yīng)用程序的。這個(gè)論壇使用到了ENode, EQueue兩個(gè)框架,EQueue是一個(gè)分布式的消息隊(duì)列組件,該組件的主體思想是參考阿里的RocketMQ。當(dāng)我們使用EQueue時(shí),面向的不是Queue,而是Topic。EQueue也是完全用C#實(shí)現(xiàn)的,關(guān)于EQueue詳細(xì)介紹,大家可以看一下這篇文章。
ENode, EQueue, Forum 開源項(xiàng)目地址
- ENode開源地址:https://github.com/tangxuehua/enode
- EQueue開源地址:https://github.com/tangxuehua/equeue
- ECommon開源地址:https://github.com/tangxuehua/ecommon
- Forum開源地址:https://github.com/tangxuehua/forum
- Forum論壇線上地址(臨時(shí)域名,以后會(huì)改為enode.me):http://enode.cloudapp.net
- Forum論壇的equeue消息數(shù)據(jù)監(jiān)控統(tǒng)計(jì)頁(yè)面:http://enode.cloudapp.net/equeueadmin
另外,項(xiàng)目中如果要開發(fā)引用程序集,可以通過Nuget來獲取,輸入關(guān)鍵字ENode就能看到所有相關(guān)的Package了,如下圖所示:
Forum總體架構(gòu)分析
Forum采用DDD+CQRS+Event Sourcing的架構(gòu)。借助于ENode,使得Forum本身無須再做技術(shù)架構(gòu)方面的設(shè)計(jì)了,直接使用ENode就能完成這種架構(gòu)。所以我們只要明白了ENode的架構(gòu),就知道這個(gè)Forum的架構(gòu)是怎樣的了。以下是ENode的架構(gòu)圖(已經(jīng)理解了這個(gè)圖的朋友請(qǐng)直接跳過這一節(jié)):
上圖是一個(gè)CQRS架構(gòu)的數(shù)據(jù)流向圖。UI請(qǐng)求會(huì)分為兩類:Command和Query。
Command用于寫數(shù)據(jù),Query用于讀數(shù)據(jù),寫數(shù)據(jù)和讀數(shù)據(jù)完全采用不同的架構(gòu)實(shí)現(xiàn)。寫數(shù)據(jù)支持同步和異步的方式,讀數(shù)據(jù)完全走簡(jiǎn)單高效思路來實(shí)現(xiàn)。當(dāng)我們要對(duì)系統(tǒng)做寫操作時(shí),如果你是用ASP.NET MVC來開發(fā)站點(diǎn),那就可以在Controller中創(chuàng)建并發(fā)送一個(gè)Command即可。該Command會(huì)被發(fā)送到消息隊(duì)列(EQueue中),然后消息隊(duì)列的訂閱方,也就是處理Command的進(jìn)程會(huì)拉取這些Command,然后調(diào)用Command Handler完成Command的處理;Command Handler處理Command時(shí),是調(diào)用Domain的方法來完成相關(guān)的業(yè)務(wù)邏輯操作。Domain就是DDD中的領(lǐng)域?qū)樱?fù)責(zé)實(shí)現(xiàn)整個(gè)系統(tǒng)的業(yè)務(wù)邏輯。 然后由于是Event Sourcing的架構(gòu),所以Domain中任何聚合根的修改都會(huì)產(chǎn)生相應(yīng)的領(lǐng)域事件(Domain Event),領(lǐng)域事件會(huì)先被持久化到EventStore中,持久化如果沒有遇到并發(fā)沖突,成功后,則會(huì)被發(fā)布(Publish)到消息隊(duì)列(EQueue中),然后消息隊(duì)列的訂閱方,也就是處理Domain Event的進(jìn)程會(huì)拉取這些Domain Event,然后調(diào)用相關(guān)的Event Handler做相關(guān)的更新,比如有些Event Handler是會(huì)更新讀庫(kù)(Read DB),有些是會(huì)產(chǎn)生新的Command,這種我把它叫做流程管理器(Process Manager,也有人叫做Saga)。當(dāng)我們有時(shí)一個(gè)業(yè)務(wù)場(chǎng)景需要涉及到多個(gè)聚合根的修改時(shí),我們會(huì)需要用到Process Manager。Process Manager負(fù)責(zé)對(duì)流程進(jìn)行建模,它的原理是基于事件驅(qū)動(dòng)的流程實(shí)現(xiàn)。Process Manager處理事件,然后產(chǎn)生響應(yīng)的Command,從而完成聚合根之間的交互。一般一個(gè)流程,我們會(huì)設(shè)計(jì)一個(gè)流程聚合根以及其他的參與該流程的聚合根,Process Manager則是用于負(fù)責(zé)協(xié)調(diào)這些聚合根之間的交互。具體的例子可以看一下ENode源代碼中的BankTransferSample。
關(guān)于Query端,由于都是查詢,這些查詢都是用于UI展示數(shù)據(jù)或者為第三方接口提供數(shù)據(jù)為目的,查詢對(duì)系統(tǒng)無副作用。我們可以用我們自己任意喜歡的方式來實(shí)現(xiàn)Query端。查詢面向的是Read DB。上面提到,Read DB中的數(shù)據(jù)是通過Event Handler(老外叫Denormalizer)來更新的。
所以我們可以看到,整個(gè)架構(gòu)中,Command端和Query端的數(shù)據(jù)源是完全分離的。Command端***的結(jié)果就是Domain Event,Domain Event是持久化在Event Store中的;Query端的數(shù)據(jù)源就是Read DB,一般可以用關(guān)系型數(shù)據(jù)庫(kù)來作為存儲(chǔ)。CQ兩端的數(shù)據(jù)同步通過Domain Event來實(shí)現(xiàn)。
上圖的CQRS架構(gòu)***的好處是在架構(gòu)級(jí)別以及數(shù)據(jù)存儲(chǔ)級(jí)別,把讀寫都分離了。這樣我們可以方便的對(duì)讀或?qū)憜为?dú)做優(yōu)化。另外由于使用了Event Sourcing的架構(gòu),使得我們的Command端只要持久化了Domain Event,就意味著保存了這個(gè)Domain的所有狀態(tài)。這個(gè)特性,可以讓我們的框架有很多設(shè)計(jì)余地,比如不必考慮Domain Event和業(yè)務(wù)數(shù)據(jù)要強(qiáng)一致等問題,因?yàn)镈omain Event本身就是業(yè)務(wù)數(shù)據(jù)本身了,我們通過Domain Event隨時(shí)可以還原出任意時(shí)刻的Domain的狀態(tài)。當(dāng)我們要查詢Domain的當(dāng)前***數(shù)據(jù)時(shí),就走Query端即可。當(dāng)然,由于Query端是異步更新的,所以Query端的數(shù)據(jù)可能會(huì)有一點(diǎn)點(diǎn)延遲。這點(diǎn)也就是我們平時(shí)一直講到的最終一致性(CQ兩端的數(shù)據(jù)最終會(huì)一致)。
通過上面的架構(gòu)圖,我們知道,一個(gè)Command發(fā)出后會(huì)經(jīng)過兩個(gè)階段的處理:1)先被某個(gè)Command Service處理(調(diào)用Domain完成業(yè)務(wù)邏輯產(chǎn)生Domain Event);2)再被Event Service處理(響應(yīng)Domain Event,完成Read DB的更新或者產(chǎn)生新的Command);理解這兩個(gè)階段對(duì)理解下面的Forum的項(xiàng)目結(jié)構(gòu)很有用處。
#p#
Forum項(xiàng)目結(jié)構(gòu)分析
以上是Forum的項(xiàng)目工程結(jié)構(gòu),項(xiàng)目中包含四個(gè)宿主工程,分別是:
Forum.BrokerService:
這個(gè)工程用于宿主EQueue的Broker,整個(gè)論壇中所有的Command,Domain Event的消息,都會(huì)被放在Broker上。比如Controller發(fā)送的Command會(huì)被發(fā)送到Broker,同樣Domain產(chǎn)生的Domain Event也會(huì)被發(fā)送到Broker;然后消費(fèi)者消費(fèi)消息則都是從BrokerService拉取消息。由于該宿主工程不需要和用戶交互,所以我部署為Windows Service。
Forum.CommandService:
這個(gè)工程就是用于處理Command的進(jìn)程,同樣也部署為Windows Service。
Forum.EventService:
這個(gè)工程就是用于處理Domain Event的進(jìn)程,同樣也部署為Windows Service。
Forum.Web:
這個(gè)就是論壇的Web站點(diǎn)了,不用多講了;這個(gè)Web站點(diǎn)做的事情就是發(fā)送Command或者調(diào)用Query端的查詢服務(wù)查詢數(shù)據(jù);Web站點(diǎn)只需要依賴于Forum.Commands和Forum.QueryServices即可,因?yàn)樗恍枰l(fā)送Command和查詢數(shù)據(jù)即可。
Forum.CommandHandlers:
所有的Command Handler都在這個(gè)工程,Command Handler的職責(zé)是處理Command,調(diào)用Domain的方法完成業(yè)務(wù)邏輯;
Forum.Commands:
所有的Command都在這個(gè)工程中,每個(gè)Command都是一個(gè)DTO,會(huì)被封裝為消息發(fā)送到EQueue。
Forum.Domain:
就是論壇的領(lǐng)域?qū)恿?,所有的聚合以、工廠、領(lǐng)域服務(wù),以及領(lǐng)域事件等都在這個(gè)工程中。這個(gè)工程是整個(gè)Forum最有價(jià)值的地方,是業(yè)務(wù)邏輯所在的工程。
Forum.Domain.Dapper:
由于Domain中可能會(huì)定義一些接口,這些接口背后的持久化需要在外部實(shí)現(xiàn);如果按照經(jīng)典DDD的架構(gòu),比如倉(cāng)儲(chǔ)接口是在Domain層定義,而實(shí)現(xiàn)則是在基礎(chǔ)層(Infrastructure)中。而從經(jīng)典DDD的分層架構(gòu)圖上來看,Domain層是依賴于Infrastructure層的,但是Infrastructure層中又有一些倉(cāng)儲(chǔ)的實(shí)現(xiàn)類要依賴于Domain層;雖然我能理解這種雙向依賴,但很容易會(huì)給不少學(xué)DDD的人帶來困惑,所以我更加傾向于,把Domain看做是架構(gòu)的核心,其他一切都是Domain的外圍。這個(gè)思想其實(shí)和六邊形架構(gòu)是一個(gè)思路。就是從架構(gòu)上來看,不是上層依賴于下層,而是外層依賴于內(nèi)層;內(nèi)層通過定義出接口,外層實(shí)現(xiàn)接口,內(nèi)層只要面向自己定義的接口即可。所以基于這個(gè)思路,我會(huì)把Forum.Domain中定義的接口,如果用Dapper來實(shí)現(xiàn),那我就定義一個(gè)Forum.Domain.Dapper這樣的工程,意思是實(shí)現(xiàn)Forum.Domain.Dapper依賴于內(nèi)層的Forum.Domain。假如以后我們有一個(gè)基于EntityFramework的實(shí)現(xiàn),則只要再創(chuàng)建一個(gè)Forum.Domain.EntityFramework這樣的工程即可。所以可以看出,F(xiàn)orum.Domain.Dapper這種工程司機(jī)上是Forum.Domain對(duì)外部的適配器,F(xiàn)orum.Domain里定義好適配接口,F(xiàn)orum.Domain.Dapper這種工程實(shí)現(xiàn)這些適配接口。基于這種思想,我們的架構(gòu)就沒有了上層依賴下層的概念了,而是替換為內(nèi)外層的關(guān)系,內(nèi)層不依賴外層,外層依賴于內(nèi)層,內(nèi)層與外層直接通過適配器接口來交互,或者通過Domain Event也可以。這樣我們就不用再去糾結(jié)經(jīng)典DDD中看似雙向依賴的問題了。
Forum.Domain.Tests:
這個(gè)工程就是對(duì)Forum.Domain的一個(gè)測(cè)試工程。每個(gè)測(cè)試用例會(huì)模擬Controller發(fā)起Command,然后***檢查Domain中的狀態(tài)是否正確修改。
Forum.QueryServices:
這個(gè)工程定義了Query端的所有查詢接口,F(xiàn)orum.Web站點(diǎn)依賴于這個(gè)工程中的查詢服務(wù)接口;然后這些查詢接口的實(shí)現(xiàn)則是放在Forum.QueryServices.Dapper中。Forum.QueryServices與Forum.QueryServices.Dapper之間的關(guān)系和Forum.Domain與Forum.Domain.Dapper之間的關(guān)系類似,這里就不在重復(fù)了。
Forum.Denormalizers.Dapper:
這個(gè)工程中的就是所有的Denormalizer,Denormalizer就是負(fù)責(zé)處理Domain Event,然后更新讀庫(kù)。然后由于目前使用Dapper實(shí)現(xiàn)數(shù)據(jù)持久化,所以工程名以Dapper結(jié)尾。
Forum.Infrastructure:
這是一個(gè)基礎(chǔ)工程,存放所有基礎(chǔ)的公共的東西,比如一些業(yè)務(wù)無關(guān)的服務(wù)或配置信息或全局變量等東西;需要強(qiáng)調(diào)的是:這里的Forum.Infrastructure和經(jīng)典DDD中的Infrastructure不是同一個(gè)概念。DDD中的Infrastructure是一個(gè)邏輯上的分層,領(lǐng)域?qū)又兴械募夹g(shù)支撐實(shí)現(xiàn)都在Infrastructure中;而這里的Infrastructure,則僅僅只是一些Common的基礎(chǔ)的公用的東西,Infrastructure不是為了為其它哪一層服務(wù)的,它可以被其他任何項(xiàng)目使用;
好了,以上簡(jiǎn)單介紹了每個(gè)工程的作用和設(shè)計(jì)目的。下面我們來看看Forum的領(lǐng)域模型的設(shè)計(jì)吧!
Forum的Domain Model的設(shè)計(jì)
- 核心功能需求分析:
- 提供用戶注冊(cè)、登錄、注銷三個(gè)功能;注冊(cè)用戶時(shí)需要驗(yàn)證用戶名是否唯一;
- 提供發(fā)帖、回帖、修改帖子、修改回復(fù),以及回復(fù)的回復(fù)這些基本核心功能;
- 系統(tǒng)管理員可以對(duì)論壇版塊進(jìn)行維護(hù);
- 聚合識(shí)別:識(shí)別出來的聚合有:論壇賬號(hào)、帖子、回復(fù)、版塊這四個(gè)。
- 再分析下每個(gè)聚合我們所關(guān)心的信息:賬號(hào)的最少信息應(yīng)該有:賬號(hào)名稱+密碼;版塊要有名稱即可;帖子要有標(biāo)題、內(nèi)容、發(fā)帖人、發(fā)帖時(shí)間、所屬版塊;回復(fù)要有回復(fù)內(nèi)容、回復(fù)時(shí)間、回復(fù)人、所屬版塊,父回復(fù)(可以為空);
- 場(chǎng)景走查:注冊(cè)就是創(chuàng)建賬號(hào)(賬號(hào)唯一性的設(shè)計(jì)后面在詳細(xì)分析);登錄本質(zhì)就是調(diào)用Query端的查詢服務(wù)查找賬號(hào)是否存在,所以不需要Domain做什么處理,注銷也是;發(fā)帖就是創(chuàng)建帖子;回帖就是創(chuàng)建回復(fù);修改帖子就是對(duì)帖子聚合根做修改;修改回復(fù)就是對(duì)回復(fù)聚合根做修改;版塊添加就是創(chuàng)建一個(gè)版塊聚合根;
- 關(guān)鍵業(yè)務(wù)規(guī)則識(shí)別:1)賬號(hào)名稱不能重復(fù);2)帖子必須要有所屬版塊和發(fā)帖人;3)回復(fù)必須要有一個(gè)對(duì)應(yīng)的帖子和回復(fù)人;
- 關(guān)鍵業(yè)務(wù)規(guī)則的實(shí)現(xiàn):
- 如何實(shí)現(xiàn)賬號(hào)名稱不能重復(fù)?首先它是一條業(yè)務(wù)規(guī)則,所以必須在Domain里實(shí)現(xiàn),而不應(yīng)該在Command Handler里。然后由于Event Sourcing的架構(gòu),天生有一個(gè)缺陷就是無法實(shí)現(xiàn)唯一性約束這種需求。所以我們需要在Domain中顯式設(shè)計(jì)出可以表達(dá)聚合根索引的東西,我把它們叫做IndexStore,表示是一種聚合根索引的存儲(chǔ)。這個(gè)思路非常類似于在經(jīng)典DDD中,我們有倉(cāng)儲(chǔ)(Repository)的概念,倉(cāng)儲(chǔ)維護(hù)了所有的聚合根;而我這里的IndexStore則是維護(hù)了聚合根的索引信息。有了這個(gè)索引信息后,我們就能在注冊(cè)新賬號(hào)時(shí),在Domain中設(shè)計(jì)一個(gè)RegisterAccountService這樣的領(lǐng)域服務(wù),領(lǐng)域服務(wù)里通過AccountIndexStore來檢查賬號(hào)名稱是否重復(fù),如果不重復(fù),則將當(dāng)前賬號(hào)名稱添加到AccountIndexStore中,如果重復(fù),則報(bào)異常。另外一個(gè)非業(yè)務(wù)的點(diǎn)需要考慮,那就是如何實(shí)現(xiàn)并發(fā)注冊(cè)用戶的處理。我們可以在Command Handler中實(shí)現(xiàn)db級(jí)別的鎖(但不不需要鎖整個(gè)賬號(hào)表,而是鎖一個(gè)其他表中的某一條記錄),確保同一時(shí)刻,不會(huì)有兩個(gè)Account名稱添加到AccountIndexStore中;我們通過RegisterAccountService把“賬號(hào)名稱不能重復(fù)”的這個(gè)業(yè)務(wù)規(guī)則顯式的表達(dá)出來,從而在代碼級(jí)別體現(xiàn)領(lǐng)域內(nèi)實(shí)現(xiàn)了這個(gè)業(yè)務(wù)規(guī)則。以前,如果沒有用Event Sourcing,我們可能會(huì)依賴db的唯一索引來實(shí)現(xiàn)這個(gè)唯一性,雖然功能上也可以實(shí)現(xiàn),但實(shí)際上賬號(hào)名稱不能重復(fù)的這個(gè)業(yè)務(wù)規(guī)則沒有體現(xiàn)在領(lǐng)域內(nèi)。這點(diǎn)也是我這次通過實(shí)現(xiàn)基于Event Sourcing而實(shí)現(xiàn)的唯一性驗(yàn)證而想到的點(diǎn)。
- 帖子必須要有所屬版塊和發(fā)帖人,這條業(yè)務(wù)規(guī)則很容易保證,只要在帖子聚合根上,對(duì)版塊和發(fā)帖子判斷是否為空就行了;
- 回復(fù)必須要有一個(gè)對(duì)應(yīng)的帖子和回復(fù)人,也是同理,只要在構(gòu)造函數(shù)中判斷是否為空即可;
#p#
以注冊(cè)新用戶為例,展示代碼實(shí)現(xiàn)
客戶端JS通過angularJS提交注冊(cè)信息:
- $scope.submit = function () {
- if (isStringEmpty($scope.newAccount.accountName)) {
- $scope.errorMsg = '請(qǐng)輸入賬號(hào)。';
- return false;
- }
- if (isStringEmpty($scope.newAccount.password)) {
- $scope.errorMsg = '請(qǐng)輸入密碼。';
- return false;
- }
- if (isStringEmpty($scope.newAccount.confirmPassword)) {
- $scope.errorMsg = '請(qǐng)輸入密碼確認(rèn)。';
- return false;
- }
- if ($scope.newAccount.password != $scope.newAccount.confirmPassword) {
- $scope.errorMsg = '密碼輸入不一致。';
- return false;
- }
- $http({
- method: 'POST',
- url: '/account/register',
- data: $scope.newAccount
- })
- .success(function (result, status, headers, config) {
- if (result.success) {
- $window.location.href = '/home/index';
- } else {
- $scope.errorMsg = result.errorMsg;
- }
- })
- .error(function (result, status, headers, config) {
- $scope.errorMsg = result.errorMsg;
- });
- };
Controller處理請(qǐng)求:
- [HttpPost]
- [AjaxValidateAntiForgeryToken]
- [AsyncTimeout(5000)]
- public async Task<ActionResult> Register(RegisterModel model, CancellationToken token)
- {
- var command = new RegisterNewAccountCommand(model.AccountName, model.Password);
- var result = await _commandService.Execute(command, CommandReturnType.EventHandled);
- if (result.Status == CommandStatus.Failed)
- {
- if (result.ExceptionTypeName == typeof(DuplicateAccountException).Name)
- {
- return Json(new { success = false, errorMsg = "該賬號(hào)已被注冊(cè),請(qǐng)用其他賬號(hào)注冊(cè)。" });
- }
- return Json(new { success = false, errorMsg = result.ErrorMessage });
- }
- _authenticationService.SignIn(result.AggregateRootId, model.AccountName, false);
- return Json(new { success = true });
- }
CommandHandler處理Command:
- [Component(LifeStyle.Singleton)]
- public class AccountCommandHandler : ICommandHandler<RegisterNewAccountCommand>
- {
- private readonly ILockService _lockService;
- private readonly RegisterAccountService _registerAccountService;
- public AccountCommandHandler(ILockService lockService, RegisterAccountService registerAccountService)
- {
- _lockService = lockService;
- _registerAccountService = registerAccountService;
- }
- public void Handle(ICommandContext context, RegisterNewAccountCommand command)
- {
- _lockService.ExecuteInLock(typeof(Account).Name, () =>
- {
- context.Add(_registerAccountService.RegisterNewAccount(command.Id, command.Name, command.Password));
- });
- }
- }
RegisterAccountService領(lǐng)域服務(wù):
- /// <summary>提供賬號(hào)注冊(cè)的領(lǐng)域服務(wù),封裝賬號(hào)注冊(cè)的業(yè)務(wù)規(guī)則,比如賬號(hào)唯一性檢查
- /// </summary>
- [Component(LifeStyle.Singleton)]
- public class RegisterAccountService
- {
- private readonly IIdentityGenerator _identityGenerator;
- private readonly IAccountIndexStore _accountIndexStore;
- private readonly AggregateRootFactory _factory;
- public RegisterAccountService(IIdentityGenerator identityGenerator, AggregateRootFactory factory, IAccountIndexStore accountIndexStore)
- {
- _identityGenerator = identityGenerator;
- _factory = factory;
- _accountIndexStore = accountIndexStore;
- }
- /// <summary>注冊(cè)新賬號(hào)
- /// </summary>
- /// <param name="accountIndexId"></param>
- /// <param name="accountName"></param>
- /// <param name="accountPassword"></param>
- /// <returns></returns>
- public Account RegisterNewAccount(string accountIndexId, string accountName, string accountPassword)
- {
- //首先創(chuàng)建一個(gè)新賬號(hào)
- var account = _factory.CreateAccount(accountName, accountPassword);
- //先判斷該賬號(hào)是否存在
- var accountIndex = _accountIndexStore.FindByAccountName(account.Name);
- if (accountIndex == null)
- {
- //如果不存在,則添加到賬號(hào)索引
- _accountIndexStore.Add(new AccountIndex(accountIndexId, account.Id, account.Name));
- }
- else if (accountIndex.IndexId != accountIndexId)
- {
- //如果存在但和當(dāng)前的索引ID不同,則認(rèn)為是賬號(hào)有重復(fù)
- throw new DuplicateAccountException(accountName);
- }
- return account;
- }
- }
EventHandler處理Domain Event:
- [Component(LifeStyle.Singleton)]
- public class AccountEventHandler : BaseEventHandler, IEventHandler<NewAccountRegisteredEvent>
- {
- public void Handle(IEventContext context, NewAccountRegisteredEvent evnt)
- {
- using (var connection = GetConnection())
- {
- connection.Insert(
- new
- {
- Id = evnt.AggregateRootId,
- Name = evnt.Name,
- Password = evnt.Password,
- CreatedOn = evnt.Timestamp,
- UpdatedOn = evnt.Timestamp,
- Version = evnt.Version
- }, Constants.AccountTable);
- }
- }
- }
結(jié)束語(yǔ)
好了,大概就這些吧。好久沒寫文章了,都不知道該怎么寫了,呵呵。接下來準(zhǔn)備再好好分享下ENode,EQueue最近幾個(gè)月在不斷完善中我遇到的一些技術(shù)問題。對(duì)了,大家可以去體驗(yàn)下這個(gè)論壇的功能哦,雖然很簡(jiǎn)單,但基本的功能還是有的。http://enode.cloudapp.net/