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

計劃任務通常都喜歡使用Cron做業來完成,好比使用spring scheduler或Quartz,本模式推薦使用黑盒式的不可知事件替代Cron做業。程序員

問題

許多業務流程涉及須要在未來執行的某些操做或工做或工做負載。它能夠是一次性動做或重複動做,能夠安排在特定日期(例如聖誕節),重複日期(月的最後一個工做日)或超時(從如今起30天)。spring

咱們但願保持整個流程的領域邏輯在單個(微)服務中很好地隔離,所以這也是此操做的邏輯所在。可是有一小部分邏輯不在這項服務中:就是事實執行須要進行,以及什麼時候須要進行。數據庫

Cron是最麻煩的:它只適用於重複操做,而且外部工具幾乎不能控制。在大型系統中,cron文件可能形成巨大的混亂。更現代的解決方案容許咱們經過在代碼中調用API來安排使用,這樣可將邏輯移動到咱們本身的服務中。但從根本上說,調度程序仍然是一個單獨的服務,可是它涉及到知識比它應該知道的會更多。使用DDD術語,咱們業務流程的無所不在的語言在此服務中泄漏。咱們的系統中必須有更多類型的元素和更多的可移動部件。bash

解決

思惟轉換是:將時間流逝視爲另外一個領域事件,就像全部其餘事件同樣。畢竟,若是咱們將領域事件定義爲與業務相關的事件的粒度時間點,好比下一個工做日,月或季度。併發

在新的設計中,cron或調度程序會按期發出普通的時間段事件,例如DayHasPassed {date}午夜事件或一個 QuarterHasPassed {year, quarter}。全部感興趣的服務都會收聽此事件。他們能夠經過執行操做,增長計數器,或經過查詢某個數據庫並按日期過濾來對其作出反應,以查找有一些工做要作的項目。工具

舉例

時間管理着一切,有用的示例:可計費小時,訂閱,資源使用,租賃,累積利息,付款,報告,工資,維護計劃以及全部循環。測試

在發票到期日,咱們須要向客戶發送提醒。在舊設計中,咱們能夠設置一個調用的cron做業CheckForOverdueInvoices;在新設計中,cron DayHasPassed每隔午夜就產生一次,在InvoiceDebtCollection中監聽DayHasPassed。ui

每當此事件到達時,InvoiceDebtCollection查詢SELECT * FROM Invoice AS i WHERE DATEDIFF(i.dueDate, NOW()) >= 30。它能夠直接發送提醒,但更好的方法是發出新InvoiceBecameOverdue事件。spa

如今,該服務能夠收聽本身的事件併發送提醒。其餘服務也能夠對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事件,但對於處理幾分鐘或幾秒或更短期的系統,這是不可行的。幸運的是,對於程序員而言,在大多數企業中,「當即」一詞意味着「在工做日結束時」,或「在本週末」,或「在事情發生的月份以後的季度結束以前」 。

相關文章
相關標籤/搜索