欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
EntityFramework之領(lǐng)域驅動(dòng)設計實(shí)踐【擴展閱讀】:CQRS體系結構模式 - d...
CQRS體系結構模式

本文將對CQRS(Command Query Responsibility Segregation,命令查詢(xún)職責分離)模式做一個(gè)相對全面的介紹??梢赃@么說(shuō),CQRS打破了經(jīng)典的領(lǐng)域驅動(dòng)設計實(shí)踐,在應用CQRS的整個(gè)過(guò)程中,你將會(huì )以另一種不同的角度去考慮問(wèn)題并尋求解決方案。比如,CQRS是事件驅動(dòng)的體系結構,事件是如何產(chǎn)生如何分發(fā)又是如何處理的?事件驅動(dòng)的體系結構適用于哪些類(lèi)型的應用系統?CQRS中的倉儲,與經(jīng)典DDD中的倉儲又有何異同?等等這些問(wèn)題,都給我們留下了無(wú)限的思考空間。

背景

在講CQRS之前,我們先了解一下CQS(Command-Query Separation,命令查詢(xún))模式。名字上看,兩者沒(méi)什么差別,然而CQRS應該說(shuō)是,在DDD的實(shí)踐中引入CQS理論而出現的一種體系結構模式。CQS模式最早由著(zhù)名軟件大師Bertrand Meyer(Eiffel語(yǔ)言之父,面向對象開(kāi)-閉原則OCP提出者)提出,他認為,對象的行為僅有兩種:命令和查詢(xún),不存在第三種情況。用他自己的話(huà)來(lái)說(shuō),就是:“提問(wèn)永遠無(wú)法改變答案”。根據CQS,任何方法都可以拆分為命令和查詢(xún)兩個(gè)部分。比如,下面的代碼:

隱藏行號 復制代碼 ? 代碼
  1. private int i = 0;
  2.  
  3. private int Add(int factor)
  4. {
  5.     i += factor;
  6.     return i;
  7. }
  8.  

可以替換為:

隱藏行號 復制代碼 ? 代碼
  1. private void AddCommand(int factor)
  2. {
  3.     i += factor;
  4. }
  5.  
  6. private int QueryValue()
  7. {
  8.     return i;
  9. }
  10.  

當命令和查詢(xún)被分離的時(shí)候,我們將會(huì )有更多的機會(huì )去把握整個(gè)事情的細節。比如我們可以對系統的“命令”部分和“查詢(xún)”部分分別采用不同的技術(shù)架構,以使得系統具有更好的擴展性,并獲得更好的性能。在DDD領(lǐng)域中,Greg Young和Eric Evans根據Bertrand Meyer的CQS模式,結合實(shí)際項目經(jīng)驗,總結了CQRS體系結構模式。

結構

整個(gè)系統結構被分為兩個(gè)部分:命令部分和查詢(xún)部分。我根據自己的體會(huì ),描繪了CQRS的體系結構簡(jiǎn)圖如下,供大家參考。在討論CQRS體系結構之前,我們有必要事先弄清楚這樣幾個(gè)概念:對象狀態(tài)、事件溯源(Event Sourcing)、快照(Snapshots)以及事件存儲(Event Store)。討論的過(guò)程中你會(huì )發(fā)現,很多概念與我們之前對經(jīng)典DDD的理解相比,有著(zhù)很大的不同。

 

 

對象狀態(tài)

 

這是一個(gè)大家耳熟能詳的概念了。什么是對象狀態(tài)?在被面向對象編程(OOP)“熏陶”了很久的朋友,一聽(tīng)到“對象狀態(tài)”,馬上想到了一對對的getter/setter屬性。尤其是.NET程序員,在C# 3.0及以后版本中,引入了Auto-Property的概念,于是,對象的屬性就很容易地成為了對象狀態(tài)的代名詞。在這里,我們應該看到問(wèn)題的本質(zhì),即使是Auto-Property,它也無(wú)非是對對象字段的一種封裝,只不過(guò)在使用Auto-Property的時(shí)候,C#編譯器會(huì )在后臺創(chuàng )建一個(gè)私有的、匿名的字段(field),而Property則成為了從外部訪(fǎng)問(wèn)該字段的唯一途徑。換句話(huà)說(shuō),對象的狀態(tài)是保存在這些字段里的,對象屬性無(wú)非是訪(fǎng)問(wèn)字段的facade。在這里澄清這樣一個(gè)事實(shí),就是為了當你繼續閱讀本文的時(shí)候,不至于對事件溯源(Event Sourcing)的某些具體實(shí)現感到困惑。在Event Sourcing的具體實(shí)現中,領(lǐng)域對象不再需要具備公有的屬性,至少外界無(wú)法通過(guò)公有屬性改變對象狀態(tài)(即setter被定義為private,甚至沒(méi)有setter)。這與經(jīng)典的DDD設計相比,無(wú)疑是一個(gè)重大改變。例如,現在我要改變某個(gè)Customer的狀態(tài),如果采用經(jīng)典DDD的實(shí)現方式,就是:

隱藏行號 復制代碼 ? 代碼
  1. [TestMethod]
  2. public void TestChangeCustomerName()
  3. {
  4.     IocContainer c = IocContainer.GetIocContainer();
  5.     using (IRepositoryTransactionContext ctx = c.GetService<IRepositoryTransactionContext>())
  6.     {
  7.         IRepository<Customer> customerRepository = ctx.GetRepository<Customer>();
  8.         Customer customer = customerRepository
  9.             .Get(Specification<Customer>
  10.             .Eval(p=>p.FirstName.Equals("sunny") && p.LastName.Equals("chen")));
  11.         // Here we use the properties directly to update the state
  12.         customer.FirstName = "dax"; 
  13.         customer.LastName = "net";
  14.         customerRepository.Update(customer);
  15.         ctx.Commit();
  16.     }
  17. }
  18.  

現在,很多ORM工具都需要聚合根具有public的getter/setter,這本身就是技術(shù)實(shí)現上的一種約束,比如某些ORM工具會(huì )使用reflection,通過(guò)讀寫(xiě)對象的property來(lái)改變對象狀態(tài)。為什么ORM工具要選擇properties,而不是fields?因為這些框架不希望自己的介入會(huì )改變對象對其狀態(tài)的封裝級別(也就是訪(fǎng)問(wèn)限制)。在引入CQRS后,ORM已經(jīng)沒(méi)有太多的用武之地了,當然從技術(shù)選型的角度看,你仍然可以選擇ORM,但就像關(guān)系型數據庫那樣,它已經(jīng)顯得沒(méi)那么重要了。

事件溯源(Event Sourcing)

在某些情況下,我們不僅需要知道對象的當前狀態(tài)是什么,而且還需要知道,對象經(jīng)歷了哪些路程,才獲得了當前這樣的狀態(tài)。Martin Fowler在介紹Event Sourcing的時(shí)候,舉了個(gè)郵包跟蹤(Package Tracking)的例子。在經(jīng)典的DDD實(shí)踐中,我們只能通過(guò)Shipment.Location來(lái)獲得郵包的當前位置,卻沒(méi)辦法獲得郵包經(jīng)歷過(guò)哪些地址而最終到達當前的地址。

為了使我們的業(yè)務(wù)系統具有記錄對象歷史狀態(tài)的能力,我們使用事件驅動(dòng)的領(lǐng)域模型來(lái)實(shí)現我們的業(yè)務(wù)系統。簡(jiǎn)而言之,就是對模型對象狀態(tài)的修改,僅允許通過(guò)事件的途徑實(shí)現,外界無(wú)法通過(guò)任何其他途徑修改對象的狀態(tài)。那么,記錄對象的狀態(tài)修改歷史,就只需要記錄事件的類(lèi)型以及發(fā)生順序即可,因為對象的狀態(tài)是由領(lǐng)域事件更改的。于是,也就能理解上面所講的為什么在Event Sourcing的實(shí)現中,領(lǐng)域對象將不再具有公有屬性,或者說(shuō),至少不再具有公有的setter屬性。

當對象的狀態(tài)被修改后,我們可能希望將對象保存到持久化機制,這一點(diǎn)與經(jīng)典的DDD實(shí)踐上的考慮是類(lèi)似的。而與之不同的是,現在我們保存的已不再是某個(gè)領(lǐng)域對象在某個(gè)時(shí)間點(diǎn)上的狀態(tài),而是促使對象將其狀態(tài)改變到當前點(diǎn)的一系列事件。由此,倉儲(Repository)的實(shí)現需要發(fā)生變化,它需要有保存領(lǐng)域事件的功能,同時(shí)還需要有通過(guò)一系列保存的事件數據,重建聚合根的能力??吹竭@里,你就知道為什么會(huì )有Event Sourcing這個(gè)概念了:所謂Event Sourcing,就是“通過(guò)事件追溯對象狀態(tài)的起源(與經(jīng)過(guò))”,它允許你通過(guò)記錄下來(lái)的事件,將你的領(lǐng)域模型恢復到之前任意一個(gè)時(shí)間點(diǎn)。你可能會(huì )興奮地說(shuō):我的領(lǐng)域模型開(kāi)始支持事件回放與模型重建了!

Event Sourcing讓我們“透過(guò)現象看本質(zhì)”,使我們更進(jìn)一步地了解到“對象持久化”的具體含義,其實(shí)也就是對象狀態(tài)的持久化。只不過(guò),Event Sourcing并不是直接保存了對象的狀態(tài),而是一系列引起狀態(tài)變化的領(lǐng)域事件。

仍然以上面的更改客戶(hù)姓名為例,在引入領(lǐng)域事件與Event Sourcing之后,整個(gè)模型的結構發(fā)生了變化,以下是相關(guān)代碼,僅供參考。

隱藏行號 復制代碼 ? 代碼
  1. [Serializable]
  2. public partial class CustomerCreatedEvent : DomainEvent
  3.  {
  4.     public string UserName { get; set; }
  5.     public string Password { get; set; }
  6.     public string FirstName { get; set; }
  7.     public string LastName { get; set; }
  8.     public DateTime DayOfBirth { get; set; }
  9. }
  10.  
  11. [Serializable]
  12. public partial class ChangeNameEvent : DomainEvent
  13.  {
  14.     public string FirstName{get;set;}
  15.     public string LastName{get;set;}
  16. }
  17.  
  18. public partial class Customer : SourcedAggregationRoot
  19.  {
  20.     private DateTime dayOfBirth;
  21.     private string userName;
  22.     private string password;
  23.     private string firstName;
  24.     private string lastName;
  25.  
  26.     public Customer(string userName, string password, 
  27.         string firstName, string lastName, DateTime dayOfBirth)
  28.     {
  29.         this.RaiseEvent<CustomerCreatedEvent>(new CustomerCreatedEvent
  30.         {
  31.             DayOfBirth = dayOfBirth,
  32.             FirstName = firstName,
  33.             LastName = lastName,
  34.             UserName = userName,
  35.             Password = password
  36.             
  37.         });
  38.     }
  39.  
  40.     public void ChangeName(string firstName, string lastName)
  41.     {
  42.         this.RaiseEvent<ChangeNameEvent>(new ChangeNameEvent
  43.         {
  44.             FirstName = firstName,
  45.             LastName = lastName
  46.         });
  47.     }
  48.  
  49.     // Handles the ChangeNameEvent by using Reflection
  50.     [Handles(typeof(ChangeNameEvent))]
  51.     private void DoChangeName(ChangeNameEvent e)
  52.     {
  53.         this.firstName = e.FirstName;
  54.         this.lastName = e.LastName;
  55.     }
  56.  
  57.     // Handles the CustomerCreatedEvent by using Reflection
  58.     [Handles(typeof(CustomerCreatedEvent))]
  59.     private void DoCreateCustomer(CustomerCreatedEvent e)
  60.     {
  61.         this.firstName = e.FirstName;
  62.         this.lastName = e.LastName;
  63.         this.userName = e.UserName;
  64.         this.password = e.Password;
  65.         this.dayOfBirth = e.DayOfBirth;
  66.     }
  67. }
  68.  

上面的代碼中定義了兩個(gè)Domain Event:CustomerCreatedEvent和ChangeNameEvent。在Customer聚合根的構造函數中,直接觸發(fā)CustomerCreatedEvent以便該事件的訂閱者對Customer對象進(jìn)行初始化;而在Customer聚合根的ChangeName方法中,則直接觸發(fā)ChangeNameEvent以便該事件的訂閱者對Customer的first name和last name作修改。Customer的基類(lèi)SourcedAggregationRoot則在領(lǐng)域事件被觸發(fā)的時(shí)候通過(guò)Reflection機制獲得內部的事件處理函數,并調用該函數完成事件處理。在上面的例子中,也就是DoChangeName和DoCreateCustomer這兩個(gè)方法。在這里需要注意的是,類(lèi)似DoChangeName和DoCreateCustomer這樣的事件處理函數中,僅允許包含對對象狀態(tài)的設置邏輯。因為如果引入其它操作的話(huà),很難保證通過(guò)這些操作,對象的狀態(tài)不會(huì )發(fā)生改變。

深入思考上面的設計會(huì )發(fā)現一個(gè)問(wèn)題,也就是當模型對象變得非常龐大,或者隨著(zhù)時(shí)間的推移,領(lǐng)域事件將變得越來(lái)越多,于是通過(guò)Event Sourcing來(lái)重建聚合根的過(guò)程也會(huì )變得越來(lái)越耗時(shí),因為每一次從建都需要從最早發(fā)生的事件開(kāi)始。為了解決這個(gè)問(wèn)題,Event Sourcing引入了“快照(Snapshots)”。


快照(Snapshots)

Snapshot的設計其實(shí)很簡(jiǎn)單。標準的CQRS實(shí)現中,采用“每產(chǎn)生N個(gè)領(lǐng)域事件,則對對象做一次Snapshot”的簡(jiǎn)單規則。設計人員其實(shí)可以根據自己的實(shí)際情況定義N的取值,甚至可以選用特定的Snapshot規則,以提高對象重建的效率。當需要通過(guò)倉儲獲得某一個(gè)聚合根實(shí)體時(shí),倉儲會(huì )首先從Snapshot Store中獲得最近一次的快照,然后再在由此快照還原的聚合根實(shí)體上逐個(gè)應用快照之后所產(chǎn)生的領(lǐng)域事件,由此大大加速了對象重建的過(guò)程??煺胀ǔ2捎肎oF Memento模式實(shí)現。請注意:CQRS引入快照的概念僅僅是為了解決對象重建的效率問(wèn)題,它并不能替代領(lǐng)域事件所能表述的含義。換句話(huà)說(shuō),即使引入快照,也不能表示我們能夠將快照之前的所有事件從事件存儲(Event Store)中刪除。因為,我們記錄領(lǐng)域事件的目的,是為了Event Sourcing,而不是Snapshots。

 

事件存儲(Event Store)

通常,事件存儲是一個(gè)關(guān)系型數據庫,用來(lái)保存引起領(lǐng)域對象狀態(tài)更改的所有領(lǐng)域事件。如上所述,在CQRS結構的系統實(shí)現中,數據庫已經(jīng)不再直接保存對象的當前狀態(tài)了,保存的只是引起對象狀態(tài)發(fā)生變化的領(lǐng)域事件。于是,數據庫的數據結構非常單一,就是單純的領(lǐng)域事件數據。事件數據的寫(xiě)入、讀取都變得非常簡(jiǎn)單高速,根本無(wú)需ORM的介入,直接使用SQL或者存儲過(guò)程操作事件存儲即可,既簡(jiǎn)單又高效。讀到這里,你會(huì )發(fā)現,雖然系統是用的一個(gè)稱(chēng)之為Event Store的機制保存了領(lǐng)域事件,但這個(gè)Event Store已經(jīng)成為了整個(gè)系統數據存儲的核心。更進(jìn)一步考慮,Event Store中的事件數據是在倉儲執行“保存”操作時(shí),從領(lǐng)域模型中收集并寫(xiě)入的,也就意味著(zhù),最新的、最真實(shí)的數據仍然存在于領(lǐng)域模型中,正好符合DDD面向領(lǐng)域的思想,同時(shí)也引出了另一深層次的考慮:In Memory Domain!

 

回到結構

在完成對“對象狀態(tài)”、“事件溯源(Event Sourcing)”、“快照(Snapshots)”以及“事件存儲(Event Store)”的討論后,我們再來(lái)看整個(gè)CQRS的結構,這樣就顯得更加清楚。上文【CQRS體系結構模式】圖中,用戶(hù)操作被分為命令部分(圖中上半部分)和查詢(xún)部分(圖中下半部分)。

  1. 用戶(hù)與領(lǐng)域層的交互,是以命令的方式進(jìn)行的:用戶(hù)通過(guò)Command Service向領(lǐng)域模型發(fā)送命令。Command Service通常被實(shí)現為.NET WCF Service。Command Bus在接收到命令后,將命令指派到命令執行器由其負責執行(可以參考GoF Command模式。TBD: 可以選擇更符合CQRS實(shí)現的其它途徑)。命令執行器在執行命令時(shí),通過(guò)領(lǐng)域事件更改對象狀態(tài),并通過(guò)倉儲保存領(lǐng)域對象。而倉儲并非直接將對象狀態(tài)保存到外部持久化機制,而僅僅是從領(lǐng)域對象中獲得已產(chǎn)生的一系列領(lǐng)域事件,并將這些事件保存到Event Store,同時(shí)將事件發(fā)布到事件總線(xiàn)Event Bus
  2. Event Handler可以訂閱Event Bus中的事件,并在事件發(fā)生時(shí)作相關(guān)處理。上文在討論服務(wù)的時(shí)候,有個(gè)例子就是利用基礎結構層服務(wù)發(fā)送SMS消息,在CQRS的體系結構中,我們完全可以在此訂閱Warehouse Transferred事件,并調用基礎結構層服務(wù)發(fā)送SMS消息。Domain Model完全不知道自己的內部事件被觸發(fā)后,會(huì )出現什么情況,而Event Handler則會(huì )處理這些情況(Domain Model與基礎結構層完全解耦)
  3. 在Event Handler中,有一種特殊的Event Handler,稱(chēng)之為Synchronizer或者Denormalizer,其作用就是為了同步“Query Database”。Query Database是為查詢(xún)提供數據源的存儲機制,用戶(hù)在UI上看到的查詢(xún)數據均來(lái)源于此數據庫。因此,CQRS不僅分離了用戶(hù)操作,而且分離了數據源,這樣做的一個(gè)最大的優(yōu)點(diǎn)就是,設計人員可以根據UI的需求來(lái)配置和優(yōu)化Query Database,例如,可以將Query Database設計為一張數據表對應一個(gè)UI界面,于是,用戶(hù)查詢(xún)變得非常靈活高效。這里也可以使用DDD中的Repository結合ORM實(shí)現數據讀取,與處于Domain Layer中的Repository不同,這個(gè)Repository就是DDD中所提到的經(jīng)典型倉儲了,你可以靈活地使用規約模式。當然,你也可以不使用ORM而直接SQL甚至No SQL,一切取決于用戶(hù)需求與技術(shù)選型。我們還可以根據需要,對Synchronizer和Denormalizer的實(shí)現采用緩存,比如,對于無(wú)需實(shí)時(shí)更新的內容,可以每捕獲N個(gè)事件同步一次Query Database,或者當有客戶(hù)端query請求時(shí),再做一次同步,這也是提高效率的一種有效方法
  4. 用戶(hù)UI通過(guò)Data Proxy獲得查詢(xún)結果數據,WCF將數據以DTO的形式發(fā)送給客戶(hù)端

總結

本文介紹了CQRS模式的基本結構,并對其中一些重要概念作了注釋?zhuān)彩俏以趯?shí)踐和思考當中總結出來(lái)的內容(PS:轉載請注明出處)。學(xué)習過(guò)DDD而剛剛開(kāi)始CQRS的朋友,在閱讀一些資料的時(shí)候勢必會(huì )感到疑惑,希望本文能夠幫助到這些朋友。比如最開(kāi)始閱讀的時(shí)候,我也不知道為什么一定要通過(guò)領(lǐng)域事件去更改對象狀態(tài),而不是在對象狀態(tài)變更的時(shí)候,去觸發(fā)領(lǐng)域事件,因為當時(shí)我仍然希望能夠在Domain Model中方便地使用getter/setter,我當時(shí)也希望能夠讓Domain Model同時(shí)適應于經(jīng)典DDD和CQRS架構。在經(jīng)過(guò)多次嘗試后發(fā)現,這種做法是不合理、不可取的,也正如Udi Dahan所說(shuō):CQRS是一種模式,既然是模式,就是用來(lái)解決特定問(wèn)題的。

還是一句老話(huà):視需求而定。不要因為CQRS所以CQRS。雖然可以很大程度地提升系統性能,雖然可以使系統具有auditing的能力,雖然可以實(shí)現Domain-Centralized,雖然可以讓數據存儲變得更加簡(jiǎn)單,雖然給我們提供了很多技術(shù)選型的機會(huì ),但是,CQRS也有很多不足點(diǎn),比如結構實(shí)現較繁雜,數據同步穩定性難以得到保證,事件溯源(Event Sourcing)出錯時(shí),模型對象狀態(tài)的恢復等等。還是引用Udi Dahan的一句話(huà):簡(jiǎn)單,但不容易!

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
對CQRS架構的幾點(diǎn)疑問(wèn)
JavaScript中的this詳解
CQRS: 提升系統可維護性和性能的軟件架構模式
JS中的JSON對象
JS/TS 對數組中的對象按對象的值進(jìn)行去重
(1)MFC中關(guān)于CString 參數傳遞
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久