透過事件風暴和DDD建立微服務時優先考慮事件

banq發表於2017-01-16
本文是討論在使用DDD+CQRS+EventSourcing中事件建模的重要性,透過抓取事件建模這個線索,能夠起到綱舉目張的效果。

DDD實現中的一個非常大錯誤解釋
領域驅動設計(DDD)是一種奇妙的技術,試圖使我們的設計更接近於業務領域 。

理論上,我們在實現中越接近業務領域,我們的軟體就越好。有了這個目標,DDD在我們的行業中非常成功。

但是,作為架構師和設計師,我們在軟體架構上做出的早期決定有一種尷尬的情感。

這種對架構的強調並不是DDD的意圖。DDD一些實踐使用經驗和解釋不幸地加劇了​​這一點,到處存在對無處不在的統一語言的誤解,特別是對領域物件。

領域物件成為一個主要的問題,因為他們糾纏了軟體設計中多個方面的關注,這是打著無所不在的旗幟。 這些領域物件成為實體,訊息的有效載荷 ,表單的後臺物件.它們在系統中是混雜的,並且是糾纏的,且是系統脆弱性的來源。

當人們將域物件打包到庫中以便重用,情況變得更糟糕。為了重用大幅度增加了其重量,這其實是增加了域物件的僵化效果 ,這就是系統的脆弱部分,導致系統無法演進推進,因為這些脆弱的臃腫的領域物件無處不在地被使用。

將域物件庫進行共享是無法演進的,如果不進行鎖保護就沒有一切。

由此,出現了注重領域物件和實體的框架,其目標是使事情更容易,但不一定是使事情變得更簡單或改善您的設計 。 這個方面不好的例子包括Rails,Grails,Spring Roo,Entity Framework,N / Hibernate等。

實體、資料庫和領域類首先作為一種方法變得非常受歡迎,同時導致了這些問題,因為它成為企業分層架構中的預設架構風格 。 整個系統發展爆炸成不可改變的僵化系統原因是,領域類中有一個東西從一個領域的無所不在,變成了跨所有領域的規範。

此時你會有一個頓悟!

這個東西並不存在於設計的早期階段 ...

... 它是發生過的某個事情 。

我們有一個名字,對於在軟體設計中發生的事情,稱為事件 。

“事件優先”
對DDD頓悟是,發生的事情應該是一些軟體設計的第一個考慮。

這種較新的方法被稱為事件優先 。

事件能夠更好地捕獲領域或系統的無處不在語言。通常,描述系統的最簡單方法是根據發生的事情描述 。

發生的事情就是事件。

事實證明,如果您正在開發現有系統,或者正在開發一個新系統,這種方法效果很好。

事件風暴技術是這個旅程的第一個設計步驟 。

事件風暴
事件風暴是一個協作活動,將領域專家,技術架構師和設計師聚集在一起,發現系統或上下文的無處不在的語言。 目的是試圖捕獲系統發生的事情 ,也就是事件。

使用貼紙以一個粗略的他們可能會發生的順序重現這些事件,儘管還沒有考慮它們是怎麼發生的,,或是什麼結構支撐其發生的。

這類似於一種偵探技術,你可以認為自己是到達犯罪現場的一個偵探,只是詢問自己或團隊工作人員有關係統的事實,事實是什麼?

將你限制到事件, 使用事件描述你知道的事實 ,它們是你正在工作的系統中發生的,並起到影響效果的。


什麼是好事件?
當設計你的事件,也就是試圖使得事件這個概念從隱式變成顯式 。如果幾個低階事件表明一個更高階別的事實,那麼需要讓該事實成為一個事件。 同樣,如果一個高層次的事實可以被看作是由較小規模的事件組成,那麼確保你也有這些小事件。

有很多事件並不是一個問題。

必須讓你的事件是完全自給自足 ,自我描述 。

使您的事件技術化且實施不可知的。

一旦你擁有一套愉快的事件,當你的上下文中出現錯誤時可以瀏覽一下發生的事件。

此方法可幫助您提出問題:“我們需要知道什麼事件?” ,這是一個強大的技術,能幫助探索一些邊界條件和假設,這些條件和假設往往影響軟體構建的複雜度 。

事件是不可變的,畢竟他們是事實。


透過事件追尋因果關係
透過探究事件發生的假設前提場景,就可以獲得建立事件的因果關係圖,

沒有因果關係的事件越多 ,您在最終設計的新興架構中生成和處理這些事件的選項就越多 。

透過“有界上下文”繫結
現在是時候引入一個圍繞在無處不在的語言周圍的邊界了,也就是圍繞事件所在邊界。

“Life Preserver”圖表只是一個由幾個同心圓組成的有用工具,它們是您的“有界上下文”和“反腐敗層”的直觀表示 。

有界上下文與團隊和團隊結構相關。
一個團隊通常需要照顧和開發一個或多個有界上下文,但是具有不同目標的兩個團隊不應該理想化地放在相同的有界上下文中工作,因為通常在這種情況下變化速度的不同導致摩擦和責任稀釋。

如果一個團隊繼承一個“Heritage”(Legacy遺留)軟體系統,它應該保留在它自己的Bounded Context中,因為它將展示它自己的無處不在的語言的時間和地點以及它最初開發的團隊。不要誘惑合併遺留系統的背景到您現在系統的當前上下文中,你將稀釋無處不在的語言,需要人們理解兩個系統。

有界上下文很少作為巢狀概念 。 團隊的工作重點是朝向明確的目標,有界的上下文遵循這個模型。

最後,到結構...

首先, 發現系統中存在的無狀態服務 。 這些是不保持任何狀態消費改變以及推出事件的服務。

其次, 考慮使用Repository服務來捕獲狀態,其中用於修改狀態以及讀取狀態的模型是相同的。

警惕資料永續性技術的漏洞抽象,並使您的儲存庫與您選擇的任何技術無關。

最後如果你需要根據修改和查詢不同狀態改變效能特徵和模型 ,可以看看微服務使用CQRS的實現...

打敗狀態的複雜性
複雜性,關注的糾纏,是軟體開發專案的不那麼沉默的殺手。 它軟體中意外或偶然的複雜性的最大原因 我們可以透過組織 ,減少和封裝來去除它,並以事件優先去尋找 。

軟體意外複雜性的第二大原因是對錯誤的業務使用錯誤的資料模型。

領域驅動設計(DDD)為我們提供了用於儲存和檢索資料的簡單儲存庫模式,但不幸的是這種模式傾向於糾纏寫和讀的問題 。

如果寫和讀的模型相同,這不是一個問題,事實上它有一些真正的優勢,因為您可以確保在儲存庫中的資料集的完全一致性。

但是當寫和讀的需求不同,而且經常發生這種情況,我們往往需要更多的東西。

除了使用的模型,事實證明,“寫”和“讀”有更進一步不同的特點,比如:
1.容錯
2.效能和可擴充套件性
3.一致性

當同時操作和讀取資料時,我們最終會遇到“系統複雜性”,這又導致對系統元件的難以理解和改變。

分離關注:命令查詢責任分離
解決這些問題的第一步是分離操作和讀取時的兩個模型。 這是命令查詢責任分離(CQRS) 模式的 核心 。

這種方法的關鍵特徵是解開寫和讀模型之間的纏結,分解為兩個系統元件: 寫和讀模型 。

命令和寫入模型
寫模型捕獲報告系統中的一些已被修改的重要狀態, 這就是為什麼這個模型通常被稱為修改模型。

命令由寫入模型接收並被處理以產生髮生了什麼修改的事件。

命令表示寫入模型的事務一致操作,並應完全成功或失敗。 寫模型將維持任何需要的狀態,以任何最佳化的形式,以產生它負責的事件。

聚合的作用就是接受命令並執行它以產生一個或多個有效事件 。

事件代表完整、完全、自描述和不可變的關於系統的事實。反過來,命令理想地包含產生期望的有效事件所需要的所有內容。

在這方面,重要的是要記住,事件不僅僅是處理命令的副作用,它們也是命令的實際資料結果。 命令和聚合只能產生一組重要的事件,也就是關於我們領域的事實。

如果Command沒有包含所有必要的細節來產生Events,你該怎麼辦? 有時,命令的處理需要聚合獲得系統的其他部分的資料或者更精確的事件。有時,此資料可以來自本地(到有界上下文)檢視。

當然,這也代表存在競爭問題了,其中聚合嘗試執行命令,而必需資訊在支援檢視或本地快取記憶體中尚不可用。

在最終一致的系統中這是正常狀態,隔離和分割槽是在犧牲系統級一致性(例如在可適應性,反垃圾郵件,基於微服務的系統)中才會體現價值。如果聚合正在處理錯誤或惡意的命令,也會發生這種情況。

如果聚合處理命令失敗,那麼該命令將被報告為失敗。 然後,傳送方有責任使用其更大的業務環境知識來決定是否回退和重試命令(一次或多次使用增加的延遲是一個常見的策略),或者失敗較大的業務操作。

在某些方面,該策略類似於使用斷路器,其中電路是啟動器和聚合體之間的相互作用,並且最終電路對於試圖進行的商業操作而斷開。

在傳送方是Saga的情況下,則可能需要附加處理以便透過補償命令執行回滾。

查詢和讀取模型
寫入模型提供了一個很好的起點,但它然後提出了問題“如何查詢我的資料? 。

在CQRS中,此責任落在一個(或多個) 讀取模型中 。

讀取模型負責監聽多個不同的事件並維護狀態的最佳化版本,其可以以高效的方式滿足響應一種或多種型別的查詢的需要 。

事件作為模型之間的通訊
那麼Read Models讀模型如何更新呢? 透過訂閱由系統中的寫入模型發出的一個或多個事件流 。

讀模型感興趣的事件將由讀模型提供結果的查詢或查詢來決定。

事件是關鍵
使用CQRS依賴於設計正確的事件 。

事件是領域中無處不在語言的最重要的部分,因此應該使這些領域概念儘可能顯式。

使用事件源減少狀態的脆弱性

事件源是您記錄來自於聚合事件的地方 。

事件儲存器保證以同樣順序可靠地儲存您的事件,然後對這些事件可以查詢和重放。

將事件儲存在一個健壯和有彈性的地方,以便可以從任何時間點的事件重新建立聚合和檢視 。

這意味著聚合和檢視可能很脆弱。

快照代表了一種折衷,即縮短重放時間,可以重試版本,但資料遷移會有一定程度耦合。



Going "Events-First" for Microservices with Event

相關文章