DDD設計中的Unitwork與DomainEvent如何相容?

来源:http://www.cnblogs.com/Zachary-Fan/archive/2016/06/17/DomainEvent.html
-Advertisement-
Play Games

最近在開發過程中,遇到了一個場景,甚是棘手,在這裡分享一下。希望大家腦洞大開一起來想一下解決思路。鄙人也想了一個方案拿出來和大家一起探討一下是否合理。 一、簡單介紹一下涉及的對象概念 工作單元:維護變化的對象列表,在整塊業務邏輯處理完全之後一次性寫入到資料庫中。 領域事件:領域對象本身發生某些變化時 ...


最近在開發過程中,遇到了一個場景,甚是棘手,在這裡分享一下。希望大家腦洞大開一起來想一下解決思路。鄙人也想了一個方案拿出來和大家一起探討一下是否合理。

一、簡單介紹一下涉及的對象概念

  工作單元:維護變化的對象列表,在整塊業務邏輯處理完全之後一次性寫入到資料庫中。

  領域事件:領域對象本身發生某些變化時,發佈的通知事件,告訴訂閱者處理相關流程。

 

二、問題來了

  我認為最合理的領域事件的觸發點應該設計在領域對象內部,那麼問題來了。當這個領域對象發生變化的上下文是一個複雜的業務場景,整個流程中會涉及到多個領域對象,所以需要通過工作單元來保證數據寫入的一致性。此時其中各個產生變化的領域對象的領域事件如果實時被髮布出去,那麼當工作單元在最終提交到資料庫時,如果產生了回滾,那麼會導致發佈了錯誤的領域事件,產生未知的後果。

 

三、問題分析

  我能夠想到的方案是,這裡領域事件的發佈也通過一個類似於工作單元一樣的概念進行持續的管理,在領域對象中的發佈只是做一個記錄,只有在工作單元提交成功之後,才實際發佈其中所有的領域事件。

 

四、說乾就乾  

實現類:

 1    public class DomainEventConsistentQueue : IDisposable
 2     {
 3         private readonly List<IDomainEvent> _domainEvents = new List<IDomainEvent>();
 4         private bool _publishing = false;
 5 
 6         public void RegisterEvent(IDomainEvent domainEvent)
 7         {
 8             if (_publishing)
 9             {
10                 throw new ApplicationException("當前事件一致性隊列已被髮布,無法添加新的事件!");
11             }
12 
13             if (_domainEvents.Any(ent => ent == domainEvent))  //防止相同事件被重覆添加
14                 return;
15             
16             _domainEvents.Add(domainEvent);
17         }
18 
19         public void Clear()
20         {
21             _domainEvents.Clear();
22             _publishing = false;
23         }
24 
25         public void PublishEvents()
26         {
27             if (_publishing)
28             {
29                 return;
30             }
31 
32             if (_domainEvents == null)
33                 return;
34 
35             try
36             {
37                 _publishing = true;
38                 foreach (var domainEvent in _domainEvents)
39                 {
40                     DomainEventBus.Instance().Publish(domainEvent);
41                 }
42             }
43             finally
44             {
45                 Clear();
46             }
47         }
48 
49         public void Dispose()
50         {
51             Clear();
52         }
53     }

使用方式:

 1             var aggregateA = new AggregateRootA();
 2             var aggregateB = new AggregateRootB();
 3 
 4             using (var queue = new DomainEventConsistentQueue())
 5             {
 6                 using (var unitwork = new SqlServerUnitOfWork(GlobalConfig.DBConnectString))
 7                 {
 8                     aggregateA.Event(queue);
 9                     aggregateB.Event(queue);
10 
11                     var isSuccess = unitwork.Commit();
12                     if (isSuccess)
13                         queue.PublishEvents();
14                 }
15             }
16 
17 
18         public class AggregateRootA : AggregateRoot
19         {
20             public void Event(DomainEventConsistentQueue queue)
21             {
22                 queue.RegisterEvent(new DomainEventA());
23             }
24         }
25 
26         public class AggregateRootB : AggregateRoot
27         {
28             public void Event(DomainEventConsistentQueue queue)
29             {
30                 queue.RegisterEvent(new DomainEventB());
31             }
32         }
33 
34         public class DomainEventA : IDomainEvent
35         {
36             public DateTime OccurredOn()
37             {
38                 throw new NotImplementedException();
39             }
40 
41             public void Read()
42             {
43                 throw new NotImplementedException();
44             }
45 
46             public bool IsRead
47             {
48                 get { throw new NotImplementedException(); }
49             }
50         }
51 
52         public class DomainEventB : IDomainEvent
53         {
54             public DateTime OccurredOn()
55             {
56                 throw new NotImplementedException();
57             }
58 
59             public void Read()
60             {
61                 throw new NotImplementedException();
62             }
63 
64             public bool IsRead
65             {
66                 get { throw new NotImplementedException(); }
67             }
68         }

問題是解決了,但是標紅的這段代碼看著特別變扭,在產生領域事件的領域對象方法上需要增加一個與表達的業務無關的參數,這個大大破壞了DDD設計的初衷——統一語言(Ubiquitous Language),簡潔明瞭的表達出每個業務行為,業務交流應與代碼保持一致。像這2行表達起來如“AggregateRootA Event DomainEventConsistentQueue”這個 DomainEventConsistentQueue其實並不是領域對象,所以其並不是領域的一部分。

 

五、陷入思考

  這裡突然想到,如果在運行中的每個線程的共用區域存儲待發佈的領域事件集合,那麼不就可以隨時隨地的管理當前操作上下文中的領域事件了嗎?這裡需要引入ThreadLocal<T> 類。MSDN的解釋參見https://msdn.microsoft.com/zh-cn/library/dd642243(v=vs.110).aspx。該泛型類可以提供僅針對當前線程的全局存儲空間,正好能夠恰到好處的解決我們現在遇到的問題。

 

六、說改就改

實現類:

 1    public class DomainEventConsistentQueue : IDisposable
 2     {
 3         private static readonly ThreadLocal<List<IDomainEvent>> _domainEvents = new ThreadLocal<List<IDomainEvent>>();
 4         private static readonly ThreadLocal<bool> _publishing = new ThreadLocal<bool> { Value = false };
 5 
 6         private static DomainEventConsistentQueue _current;
 7         /// <summary>
 8         /// 獲取當前的領域事件一致性隊列。
 9         /// 由於使用了線程本地存儲變數,此處為單例模式。
10         /// </summary>
11         /// <returns></returns>
12         public static DomainEventConsistentQueue Current()
13         {
14             if (_current != null)
15                 return _current;
16             var temp = new DomainEventConsistentQueue();
17             Interlocked.CompareExchange(ref _current, temp, null);
18             return temp;
19         }
20 
21         public void RegisterEvent(IDomainEvent domainEvent)
22         {
23             if (_publishing.Value)
24             {
25                 throw new ApplicationException("當前事件一致性隊列已被髮布,無法添加新的事件!");
26             }
27 
28             var domainEvents = _domainEvents.Value;
29             if (domainEvents == null)
30             {
31                 domainEvents = new List<IDomainEvent>();
32                 _domainEvents.Value = domainEvents;
33             }
34 
35             if (domainEvents.Any(ent => ent == domainEvent))  //防止相同事件被重覆添加
36                 return;
37 
38             domainEvents.Add(domainEvent);
39         }
40 
41         public void Clear()
42         {
43             _domainEvents.Value = null;
44             _publishing.Value = false;
45         }
46 
47         public void PublishEvents()
48         {
49             if (_publishing.Value)
50             {
51                 return;
52             }
53 
54             if (_domainEvents.Value == null)
55                 return;
56 
57             try
58             {
59                 _publishing.Value = true;
60                 foreach (var domainEvent in _domainEvents.Value)
61                 {
62                     DomainEventBus.Instance().Publish(domainEvent);
63                 }
64             }
65             finally
66             {
67                 Clear();
68             }
69         }
70 
71         public void Dispose()
72         {
73             Clear();
74         }
75     }

使用方式:

 1             var aggregateA = new AggregateRootA();
 2             var aggregateB = new AggregateRootB();
 3 
 4             using (var queue = DomainEventConsistentQueue.Current())
 5             {
 6                 using (var unitwork = new SqlServerUnitOfWork(GlobalConfig.DBConnectString))
 7                 {
 8                     aggregateA.Event();
 9                     aggregateB.Event();
10 
11                     var isSuccess = unitwork.Commit();
12                     if (isSuccess)
13                         queue.PublishEvents();
14                 }
15             }
16 
17         public class AggregateRootA : AggregateRoot
18         {
19             public void Event()
20             {
21                 DomainEventConsistentQueue.Current().RegisterEvent(new DomainEventA());
22             }
23         }
24 
25         public class AggregateRootB : AggregateRoot
26         {
27             public void Event()
28             {
29                 DomainEventConsistentQueue.Current().RegisterEvent(new DomainEventB());
30             }
31         }
32 
33         public class DomainEventA : IDomainEvent
34         {
35             public DateTime OccurredOn()
36             {
37                 throw new NotImplementedException();
38             }
39 
40             public void Read()
41             {
42                 throw new NotImplementedException();
43             }
44 
45             public bool IsRead
46             {
47                 get { throw new NotImplementedException(); }
48             }
49         }
50 
51         public class DomainEventB : IDomainEvent
52         {
53             public DateTime OccurredOn()
54             {
55                 throw new NotImplementedException();
56             }
57 
58             public void Read()
59             {
60                 throw new NotImplementedException();
61             }
62 
63             public bool IsRead
64             {
65                 get { throw new NotImplementedException(); }
66             }
67         }

這樣代碼看起來比之前優雅多了。這裡的 DomainEventConsistentQueue.Current() 中操作的變數針對同一個線程在哪都是共用的,所以我們只管往裡丟數據就好了~

 

七、方案的局限性。

  對於執行上下文的要求較高,整個領域事件的發佈必須要求在同一線程內操作。所以在使用的過程中儘量避免這種情況的發生。如果實在無法避免只能通過把DomainEventConsistentQueue 當作變數在多個線程之間傳遞了。

 

以上是個人的想法,可能有所考慮不周~ 不知道各位園子里的小伙伴們是否有處理過類似場景的經驗,歡迎留言探討,相互學習~

  

作者: Zachary_Fan
出處:http://www.cnblogs.com/Zachary-Fan/p/5586887.html 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這篇文章主要介紹了Java實現時間動態顯示方法彙總,很實用的功能,需要的朋友可以參考下 本文所述實例可以實現Java在界面上動態的顯示時間。具體實現方法彙總如下: 1.方法一 用TimerTask: 利用java.util.Timer和java.util.TimerTask來做動態更新,畢竟每次更新 ...
  • 函數又叫方法,是實現某項功能或完成某項任務的代碼塊 #include<stdio.h>void show(){ printf("I like c language");}int main(){ show(); return 0;} 上面的show()是自定義函數, int main()的int是要求 ...
  • 分析PHP腳本Xdebug內置分析器能讓你找到腳本中的瓶頸並用額外的工具諸如KcacheGrind或WinCacheGrind工具可視化。 介紹 Xdebug分析器是分析PHP代碼和判斷瓶頸或確定代碼哪裡運行過慢需要使用加速器的強大分析器。Xdebug2的分析器輸出信息以cachegrind相容文件 ...
  • 批量插入sql語句: INSERT INTO table (field1,field2,field3) VALUES ('a',"b","c"), ('a',"b","c"),('a',"b","c") mybatis通過foreach迴圈拼裝瞭如上的sql語句。 一、xml 說明: mysql批量 ...
  • /**寶寶我英語不好,後面註釋拼音 請見諒**/ 1.Linux 開源的操作系統,在伺服器端用戶數量非常大,很多伺服器都是使用Linux系統運行的。 相對windows系統來說具有非常完善的用戶許可權系統,安全繫數非常高 2.Cygwin (cwin) 在windows平臺模擬Linux環境 3.Ap ...
  • 在讀這個模式,頭腦里就浮想兩個問題: 1. JavaScript的原型模式與普遍的原型模式有什麼區別? 2. JavaScript的原型模式與prototype有什麼關係? 原型模式定義 原型模式(創建型設計模式)是用一個對象做模板,克隆出新對象。 另外原型模式中的克隆分為"淺克隆"和"深克隆": ...
  • 在公司ERP項目開發中,遇到批量數據插入或者更新,因為每次連接資料庫比較耗時,所以決定改為批量操作,提升效率。庫存檔點導入時,需要大量數據批量操作。 1:資料庫連接代碼中必須開啟批量操作。加上這句,&allowMultiQueries=true,完整的如下: jdbc:mysql://localho ...
  • 寫了關於Hadoop的Map側join 和Reduce的join,今天我們就來在看另外一種比較中立的Join。 SemiJoin,一般稱為半鏈接,其原理是在Map側過濾掉了一些不需要join的數據,從而大大減少了reduce的shffule時間,因為我們知道,如果僅僅使用Reduce側連接,那麼如果 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...