分散式系統解耦模式:用事件代表時間觸發Cron計劃任務

banq發表於2019-05-13

計劃任務一般都喜歡使用Cron作業來完成,比如使用spring scheduler或Quartz,本模式推薦使用黑盒式的不可知事件替代Cron作業。

問題
許多業務流程涉及需要在將來執行的某些操作或工作或工作負載。它可以是一次性動作或重複動作,可以安排在特定日期(例如聖誕節),重複日期(月的最後一個工作日)或超時(從現在起30天)。
我們希望保持整個流程的領域邏輯在單個(微)服務中很好地隔離,因此這也是此操作的邏輯所在。但是有一小部分邏輯不在這項服務中:就是事實執行需要進行,以及何時需要進行。

Cron是最麻煩的:它只適用於重複操作,並且外部工具幾乎不能控制。在大型系統中,cron檔案可能造成巨大的混亂。更現代的解決方案允許我們透過在程式碼中呼叫API來安排使用,這樣可將邏輯移動到我們自己的服務中。但從根本上說,排程程式仍然是一個單獨的服務,但是它涉及到知識比它應該知道的會更多。使用DDD術語,我們業務流程的無所不在的語言在此服務中洩漏。我們的系統中必須有更多型別的元素和更多的可移動部件。

解決
思維轉換是:將時間流逝視為另一個領域事件,就像所有其他事件一樣。畢竟,如果我們將領域事件定義為與業務相關的事件的粒度時間點,比如下一個工作日,月或季度。

在新的設計中,cron或排程程式會定期發出普通的時間段事件,例如DayHasPassed {date}午夜事件或一個 QuarterHasPassed {year, quarter}。所有感興趣的服務都會收聽此事件。他們可以透過執行操作,增加計數器,或透過查詢某個資料庫並按日期過濾來對其做出反應,以查詢有一些工作要做的專案。

舉例
時間管理著一切,有用的示例:可計費小時,訂閱,資源使用,租賃,累積利息,付款,報告,工資,維護計劃以及所有迴圈。

在發票到期日,我們需要向客戶傳送提醒。在舊設計中,我們可以設定一個呼叫的cron作業CheckForOverdueInvoices;在新設計中,cron DayHasPassed每隔午夜就產生一次,在InvoiceDebtCollection中監聽DayHasPassed。
每當此事件到達時,InvoiceDebtCollection查詢SELECT * FROM Invoice AS i WHERE DATEDIFF(i.dueDate, NOW()) >= 30。它可以直接傳送提醒,但更好的方法是發出新InvoiceBecameOverdue事件。
現在,該服務可以收聽自己的事件併傳送提醒。其他服務也可以對InvoiceBecameOverdue 做出反應,例如調整收入預測或暫停帳戶。
時間流逝事件也可以使用在更具特定領域。納斯達克的盤前交易時間為04:00至09:30,然後是正常交易時間至16:00,盤後交易時間為20:00。假期沒有交易。服務可以為每個啟動和關閉生成事件,可以由許多感興趣的服務使用。

優點
在上面的示例中,事件日誌將顯示:

CustomerWasInvoiced
DayHasPassed
DayHasPassed
...
InvoiceBecameOverdue
ReminderWasSent
AccountWasSuspended


使用時間元素編寫業務流程測試非常優雅:

場景: If no payment is received after 15 days, we suspend the account
Given CustomerWasInvoiced
  And DayHasPassed 
  And DayHasPassed 
  And (...) 
 When DayHasPassed
 Then InvoiceBecameOverdue
  And AccountWasSuspended 
  
場景: If payment is received in time, we do nothing
Given CustomerWasInvoiced
  And DayHasPassed 
  And DayHasPassed 
 When PaymentWasMade
 Then Nothing        


更重要的是,這是一種很好的反應方法。當服務將命令傳送到另一個服務時,它需要知道該其他服務接受該命令。當我們所做的只是傳送通用時間事件通道時,排程程式不需要知道誰在聽,消費者應該如何反應,或者是否還有任何服務可以監聽。所有決策和領域知識都歸接收者所有。這是很好的脫鉤。

它也是時間解耦:排程程式可以將此時間通道事件放在佇列中,並且此時消費者是否可用來處理事件並不重要。儘管如此,消費者可能會停頓幾天,然後只需趕上並處理DayHasPassed佇列中的所有事件。
在事件採購中,您只需將DayHasPassed事件儲存在事件儲存中,這樣您就可以完全按照時間發生的方式回放整個歷史記錄,而不依賴於外部源。

時間事件和其他領域事件型別使用完全一樣的格式和協議,透過與其他所有內容相同的訊息傳遞基礎結構傳送它 這使得該模式在實現方面非常便宜。

弱點
原來在另一個其他地方上存在的一些領域知識:用於計算何時需要發生的域邏輯,例如“每月10日”,現在已經有效地從cron或排程程式轉移到服務中。在實踐中,這不是什麼大不了的事,因為你可以找到時間庫來為你完成工作。從好的方面來說,“除了週末,滿月期間或年度辦公室聚會期間,”每個月的第10個月“都是排程員無論如何也不會為你做的事情。

值得注意的是,您不希望將時間段事件模式用於實時系統。我們可以輕鬆地實現每年365個DayHasPassed事件,但對於處理幾分鐘或幾秒或更短時間的系統,這是不可行的。幸運的是,對於程式設計師而言,在大多數企業中,“立即”一詞意味著“在工作日結束時”,或“在本週末”,或“在事情發生的月份之後的季度結束之前” 。

 

相關文章