2017-03-01 63 views
1

場景:CQRS +微服務:如何處理關係/驗證?

  • 我有2個微服務(兩者使用CQRS +事件採購內部)
  • 微服務1管理聯繫人(=聚合根)
  • 微服務2管理髮票(=聚合根)

發票的收件人必須是有效的接觸。

CreateInvoiceCommand:

{ 
    "content": "my invoice content", 
    "recipient": "42" 
} 

我現在讀的時候很多的,該寫邊(=命令處理程序)不應調用read側。


考慮到這一點,發票微服務必須聽取所有ContactCreatedContactDeleted事件,以便知道如果給定的收件人ID有效。

然後我有成千上萬的發票微服務中的聯繫人,即使我知道,只有他們幾個人都不會收到發票。


有處理這些情況的最佳做法嗎?

回答

1

發票的收件人必須是有效的接觸。

這是一個業務規則。應該問這個問題,這個業務規則對我的應用程序意味着什麼?誰應該爲實施這一規則承擔責任,還是可以分擔責任?

一種可能是,是的,業務規則的有關發票,因此應在發票服務來實現它的責任。

但是,業務規則實際上是關於發票的創建。而且,在您的架構中創建發票的所有者奇怪地不是發票服務。原因是命令的名稱是CreateInvoiceCommand

讓我們考慮一下 - 發票服務將永遠不會自行創建發票。它只是提供了能力。在此體系結構中,發票創建的實際所有者是命令的發送者。

使用這種推理,如果業務規則是說不可能發生針對無效收件人創建發票,然後就變成了命令發送者的責任,以確保該業務規則執行。

這將是一個非常不同的場景,而不是接收命令,訂閱事件的發票服務。例如,一個名爲WidgetSold的事件。在這種情況下,創建發票的所有者顯然是發票服務,因此業務規則將在那裏實施。

如果用戶點擊了接觸42按鈕創建發票,這是 用戶的責任要小心,聯繫42存在

是的,這是正確的。用戶的意圖是創建發票。因此,關於發票創建的業務規則應該在此時執行。這是怎麼發生的(或者這是否發生)是一個不同的問題。

但是,如果用戶不在乎呢?然後它會創建一個發票 與一個無效的收件人ID。

也正確。正如你所說,這種方法有副作用,其中之一就是最終可能導致整個系統的數據不一致。這是SOA的現實之一。

是不是有點類似於此:發票有一個貨幣代碼 屬性,它是一個字符串。

我不知道我是否同意。詢問這是一個有效的ISO貨幣嗎?不同於要求根據另一個系統是實體42有效嗎?。我會這樣想的。

是不是有點一樣給收件人不是null,而是根據我的聯繫人數據庫有效 ?

我同意在現實中,您可以在服務中實現此驗證。我只是說我認爲它不適合它。如果您想這樣做,則必須在本地調出另一項服務或將所有聯繫人存儲在本地,因爲您最初是在框問題。我認爲在服務之外做這件事更簡單。

+0

謝謝。這聽起來很有趣也很合理。讓我總結一下,看看我是否真的明白了你的觀點:如果用戶點擊'contact 42' *按鈕創建發票,用戶有責任注意'contact 42'存在。看起來很簡單。 **但是**如果用戶不關心該怎麼辦?然後它會創建一個包含無效收件人ID的發票。意思是:我的數據庫中有垃圾。它不好嗎?是的,但只限於可用磁盤空間。但它不會爲應用程序本身造成任何麻煩,因爲它只是一個孤立的數據。 –

+0

@BenjaminM我已更新我的回答 –

+0

好的。 (希望)唯一我仍然不明白的是:爲什麼*有效聯繫*業務規則,而不應該成爲發票的CommandHandler的一部分?這是不是有點類似於這個:發票有'currencyCode'屬性,它是一個字符串。在我看來,CommandHandler現在將驗證提供的'currencyCode'是否爲空,並且根據ISO 4217是有效的。是否它與*給定的收件人不是null並且根據我的Contacts Database *有效?也許我完全錯了。請賜教。 :) –

1

我認爲答案取決於你希望系統有多強大,也就是說,如何處理在這種情況下Contacts Microservice關閉(無響應或非常慢)。

1.你想成爲非常有彈性

如果Contacts Microservice下跌,你希望能夠發出發票一些(也許是大多數)的接觸。在這種情況下,您可以收聽ContactCreatedContactDeleted並保持一個(最終一致的)本地聯繫人列表有效的聯繫人;他們應該在這個有界的上下文中被命名爲無處不在的語言,如Payers(或類似的東西)。然後,在應用層中,在構建CreateInvoiceCommand時,檢查Payer是否有效並創建該命令。

2.你並不需要是有彈性的

如果Contacts Microservice下跌,你拒絕生成發票。在這種情況下,在構建命令時,您向Invoices Microservice API端點發出請求,並驗證Payer是否有效。

在任何情況下,您都會在調度命令之前檢查聯繫人的有效性。

+0

謝謝。你的第一點聽起來像*它可以從寫入端*調用讀取端。但我到處讀到應該避免。還是他們只是說,你不應該稱自己的讀方? (即InvoiceCommandHandler不應執行InvoiceQuery)? ......爲什麼在命令發出之前檢查有效性*?我認爲驗證應該是命令處理程序的一部分? –

+0

驗證有多種類型 –

+0

您之前檢查過,因爲Aggregate無法查詢外部服務。聚合必須僅基於先前事件和當前命令來執行不變量。它應該是純粹的。 –

3

發票的收件人必須是有效的聯繫人。

所以,你首先需要的是知道的 - 如果兩個實體是不同集合的一部分,你不能真正實現「將更改應用於僅實體滿足規範這個實體」,因爲實體可能會在您評估規格的那一刻和執行寫入的那一刻之間發生變化。

換句話說 - 你只能得到最終跨越一個總界限的一致性。

聚集是其自身狀態的權限,但其他所有內容(例如命令消息的內容),它幾乎不得不接受某些外部權限已檢查數據。

有幾個方法,你可以採取這裏

1)您可以盲目地接受,在命令中指定的收件人是否有效。

2)您可以嘗試從接收來自某個外部授權機構(又名:某個其他彙總的讀取模型)的收件人的有效性,並將其從不可信來源提交併提交給域模型。

3)您可以盲目接受所述的命令,但將發票視爲暫時的,直到收件人的有效性得到確認。這意味着在發票上運行第二個命令來驗證收件人。

注 - 從視圖模型的角度來看,這些不同的命令是等價的,但在應用層,他們並不需要是 - 你可以限制訪問命令可信來源(不作它是公共api的一部分,需要僅可用於可信來源的授權等)。

方法#3是最微小的方法,因爲這兩個命令可以及時分開 - 您可以在CreateInvoice命令到達時立即接受,並以異步方式驗證收件人。

你會在哪裏把方法4),其中發票Microservice有它自己的聯繫人存儲,每當有一個ContactCreated或ContactDeleted事件時得到更新?那麼這兩個實體都是同一個服務和邊界的一部分。現在應該有可能讓事情保持一致,對嗎?

號你所做的相同服務的兩個實體的一部分,但問題是從來沒有,他們在不同的服務,但它們是在不同的聚集 - 這意味着我們可以同時更改實體狀態,這意味着我們無法確保它們立即同步。

如果你想立即保持一致性,你需要一個模型,以不同的方式繪製你的邊界。

例如,如果將發票實體建模爲聯繫人聚合的一部分,則聚合可以確保新發票需要有效收件人的不變量 - 域模型使用內存中狀態的副本來確認收件人,當我們加載時有效,並且寫入記錄簿確認記錄簿自加載發生以來未發生變化。

合計狀態的寫入是記錄簿中的比較和交換;如果某個併發進程已使接收方無效,則CAS操作將失敗。

當然,折衷是任何更改爲聯繫人彙總也會導致發票失敗;使用同一收件人同時編輯不同的發票將退出該窗口。

聚合是全部或沒有;他們不可分離。

現在,一個可能是您的發票集合中有一部分必須立即與收件人保持一致,另一部分最終一致或甚至不一致可接受。在這種情況下,您的目標是重構模型。

+0

謝謝!你會把**方法4)**放在哪裏,其中Invoices Microservice有自己的Contacts Store,每當有'ContactCreated'或'ContactDeleted'事件時它都會被更新?那麼這兩個實體都是同一個服務和邊界的一部分。現在應該有可能讓事情保持一致,對嗎? (除了命令包含新創建的聯繫人,但Invoices Microservice尚未處理'ContactCreated'事件 - 這會使事情最終保持一致?!) –