跳至內容

領域事件

領域事件是一種解耦機制,讓各功能無需直接依賴就能互相溝通。功能 A 不再呼叫 功能 B,而是由功能 A 發佈事件,功能 B 訂閱該事件。

DomainEventBus 是一個進程內的廣播式發佈/訂閱匯流排。發佈方呼叫 publish(event);訂閱方則消費帶類型的 on<T>() 串流。每個事件都實作 DomainEvent,並攜帶 occurredAt。

匯流排運行於 cc_server 內部。 每個發佈方與每個存活中的訂閱方都在其中。 沒有任何用戶端能看到這條匯流排;用戶端看到的是伺服器重新以 notifications/* JSON-RPC 訊框發出的精選子集。

沒有事件,每一個跨功能的反應都會變成一次匯入。通知路徑為了知道何時該彈出提示, 就得匯入代理功能、pull request 功能、工單與會議——而這些功能反過來又都必須知道 通知的存在。工單功能還得匯入管線,好讓已完成的工單推進一次執行。依賴圖最終纏成 死結,共享核心也不再共享。

發佈事件打破了這一點。通知路徑訂閱二十一個事件類別,卻完全不匯入引發這些事件的 任何功能。管線觸發器調度器訂閱整條匯流排,對 pull request、工單或會議一無所知。 可觀測性會彙總執行結果,而無需介入派發。

代價則是老問題:發佈方無法得知有沒有人在監聽,而一個從未被建構出來的監聽者會 悄無聲息地處於惰性狀態。TicketRemoteSyncHandler 就是現成的例子:它已被宣告、 有測試覆蓋,卻從未接入生產環境。

有一件事匯流排_並不_負責,值得把話說清楚。

稽核軌跡並不是事件串流的投影。 每個會變更狀態的 RPC 操作預設都會被稽核—— RepoOp.audited 預設為 true,操作只有明確宣告 audited: false 才能退出稽核, 而這是寫在操作本身、顯而易見的刻意之舉。調度器會在呼叫成功後,直接追加一筆 「誰在何處做了甚麼」的記錄。並不存在 DomainEventAuditBridge。 ActivityLogPersister 會從 ActivityLogged 事件寫入 activity_log 記錄;那是與 RPC 稽核相互獨立的另一條資料流。

事件匯流排_確實_是通知與自動化的傳輸通道,但它不是負責記帳的權威記錄系統。

  • 通知。 伺服器會把二十一個事件類別轉成 notifications/* 傳輸訊框,記錄到 每個工作區各自持久的通知動態,並廣播至已連線的工作階段。若某個訊框的 workspace_id 指向的工作區,其工作階段使用者並非成員,該訊框會在伺服器上被 丟棄;用戶端隨後再把其餘訊框篩選為它正在查看的那個工作區。
  • 事件驅動自動化。 管線觸發器訂閱匯流排,並自動啟動相符的管線(見下文)。
  • 生命週期反應。 建立工作區會為其初始化 CEO 與專家代理、內建管線範本,以及 入門評估套件。代理執行完成後,會恢復已暫停的管線步驟、關閉任務、向目標監督者 提供輸入、可喚醒指定的檢查者,還可能重新觸發團隊負責人。PR 合併或空間被刪除 會觸發工作樹垃圾回收。
  • 跨廠商同步。 一個監聽五種工單事件的協調器,會把本地工單變更推送到已配置的 廠商。

事件遍及整個產品面。概括而言:

  • 工作區、代理與程式庫——工作區建立、執行完成、程式庫註冊、技能更新
  • Pull request——發佈、狀態變更、合併、審查請求、提及、自己提交的 PR 監看、外部偵測到的 PR
  • 訊息——訊息、空間與空間佈建進度
  • 工單——建立、指派、改派、詳情、狀態以及三種終態結果
  • 任務——為單次派發的執行提供有序的生命週期訊框,讓用戶端能排序與去重
  • 管線——只有執行的終態事件(已完成、已取消、已失敗)
  • 可觀測性——活動日誌項目與預算門檻
  • 身分與成員關係——成員新增、移除、角色變更
  • 日曆與會議——認證到期、會議開始與錄製完成
  • 測試機——接管、回收與意外關閉

記憶、編排、計劃文件、產出物、核准、工單同步接收、日曆重新整理、工作樹合併、 使用者建立、邀請與裝置撤銷都不是領域事件;請參閱參考頁。

完整的目錄——全部 53 個具體事件類別、它們的載荷,以及消費這些事件的 24 個訂閱方——請參閱 領域事件參考。那一頁是清單,這一頁是模型。

成員關係與裝置事件看似對稱,實則不然;如果你在推敲撤銷要多快才生效,這一點就 很重要。

撤銷裝置確實會終止它的工作階段。 伺服器會監視已配對的裝置表,只要某台裝置 離開了使用中集合,其對應的使用中工作階段就會在數秒內被中斷。

移除成員並不會關閉 Socket。 WorkspaceMemberRemoved 會被消費: LocalRpcServer 與 RemoteRelayHost 會移除該使用者已掛載的工作區訂閱, RemoteEventForwarder 則會使其成員關係快取失效,因此不會再推送該工作區的後續 訊框。真正攔下下一次 RPC 的仍然是角色閘門,它會在每一次呼叫時重新解析成員 關係——因此他們的下一個請求會被拒絕,並顯示「Not a member of this workspace」。 對他們嘗試做的任何事而言,效果都是即時的;只是這並非靠掛斷連線達成。

管線觸發器訂閱領域事件,並自動啟動相符的管線。PipelineTriggerDispatcher 會監聽整條匯流排,然後:

  1. 除非事件類型是它知道如何對應到載荷的類型,否則直接短路返回
  2. 在每一個工作區中,查找與該事件類型相符的已啟用觸發器
  3. 把每個觸發器篩選到其自身的工作區,並套用它的載荷相符篩選條件
  4. 為每一個通過篩選的觸發器啟動一次執行

關鍵限制在第 1 步。匯流排承載 53 種事件類型;新增觸發器選擇器只會為以下十四種 啟動執行(EventPayloadMapper 的載荷分支): ExternalPrDetected, PullRequestPublished, PullRequestStatusChanged, PrMerged, MessageReceived, TicketAssigned, TicketCompleted, TicketFailed, TicketCancelled, BudgetThresholdCrossed, RepoAdded, MeetingRecordingStopped, SkillUpdated 以及 SpaceDeleted。除此之外的任何事件都無法啟動管線。

RemoteEventForwarder 會把通知類事件轉成傳輸訊框,並透過 RPC 推送到已連線的 工作階段,在那裡由訊框映射器將它們轉成應用程式內通知。因此,精簡用戶端看到的 是伺服器事件串流的一個投影,本身並不承擔任何執行——這正是 精簡用戶端拆分的重點所在。

不過,並非每個推送的訊框都是通知。任務生命週期串流與工單改派會即時轉發以驅動 UI,但它們不會寫入通知動態,也不會彈出任何提示。