콘텐츠로 이동

도메인 이벤트

도메인 이벤트는 기능들이 직접 의존하지 않고도 통신할 수 있게 해 주는 결합 해제 메커니즘입니다. 기능 A가 기능 B를 호출하는 대신, 기능 A가 이벤트를 발행하고 기능 B가 그것을 구독합니다.

DomainEventBus는 프로세스 내 브로드캐스트 발행/구독 버스입니다. 발행자는 publish(event)를 호출하고, 구독자는 타입이 지정된 on<T>() 스트림을 소비합니다. 모든 이벤트는 DomainEvent를 구현하며 occurredAt을 함께 가집니다.

버스는 cc_server 안에서 실행됩니다. 모든 발행자와 살아 있는 모든 구독자가 그곳에 있습니다. 어떤 클라이언트도 이 버스를 보지 못합니다. 클라이언트가 보는 것은 서버가 notifications/* JSON-RPC 프레임으로 다시 내보내는 선별된 부분 집합입니다.

이벤트가 없으면 기능 간의 모든 반응이 import가 됩니다. 알림 경로는 토스트를 언제 띄울지 알기 위해서만 해도 에이전트 기능, 풀 리퀘스트 기능, 티켓팅, 회의를 import해야 하며, 그러고 나면 그 각각이 알림이 존재한다는 사실을 알아야 합니다. 티켓팅은 완료된 티켓이 실행을 진행시키도록 파이프라인을 import해야 합니다. 의존성 그래프는 매듭으로 닫히고, 공유 커널은 더는 공유되지 않게 됩니다.

이벤트를 발행하면 그 고리가 끊어집니다. 알림 경로는 21개의 이벤트 클래스를 구독하며, 그 이벤트를 발생시키는 기능은 하나도 import하지 않습니다. 파이프라인 트리거 디스패처는 버스 전체를 구독하며 풀 리퀘스트, 티켓, 회의에 대해 아무것도 알지 못합니다. 관측성은 디스패치 내부에 손을 뻗지 않고 실행 결과를 집계합니다.

대가는 늘 그렇듯이 다음과 같습니다. 발행자는 누군가 듣고 있는지 알 수 없고, 한 번도 생성되지 않은 리스너는 소리 없이 아무 일도 하지 않습니다. TicketRemoteSyncHandler가 늘 나오는 예입니다. 선언되어 있고 테스트도 갖추고 있지만 프로덕션에는 연결되지 않습니다.

버스가 소유하지 않는 한 가지는 분명히 해 둘 가치가 있습니다.

감사 추적은 이벤트 스트림의 투영이 아닙니다. 상태를 변경하는 모든 RPC 작업은 기본적으로 감사됩니다. RepoOp.audited의 기본값은 true이고, 작업이 감사를 받지 않으려면 audited: false를 선언해야만 합니다. 이는 작업 자체에 드러나는 의도적인 행위입니다. 디스패처는 호출이 성공한 직후에 “누가 무엇을 어디서 했는지” 행을 덧붙입니다. DomainEventAuditBridge는 없습니다. ActivityLogPersister는 ActivityLogged 이벤트로부터 activity_log 행을 기록하며, 이는 RPC 감사와는 별개의 피드입니다.

이벤트 버스는 알림과 자동화의 전송 수단이 맞습니다. 기록의 원장 시스템은 아닙니다.

  • 알림. 서버가 21개의 이벤트 클래스를 notifications/* 와이어 프레임으로 바꾸어 워크스페이스별 영속 피드에 기록하고 연결된 세션에 브로드캐스트합니다. workspace_id가 세션 사용자가 속하지 않은 워크스페이스를 가리키는 프레임은 서버에서 버려지고, 클라이언트는 나머지를 자신이 보고 있는 워크스페이스로 필터링합니다.
  • 이벤트 기반 자동화. 파이프라인 트리거가 버스를 구독하고 일치하는 파이프라인을 자동으로 시작합니다(아래 참고).
  • 수명주기 반응. 워크스페이스를 만들면 CEO와 전문 에이전트, 내장 파이프라인 템플릿, 시작용 평가 스위트가 시딩됩니다. 완료된 에이전트 실행은 일시 중단된 파이프라인 단계를 재개하고, 작업을 닫고, 목표 슈퍼바이저에 정보를 공급하며, 지정된 검사자를 깨울 수 있고, 팀 리더를 다시 트리거할 수 있습니다. 병합된 PR이나 삭제된 스페이스는 워크트리 가비지 컬렉션을 트리거합니다.
  • 공급업체 간 동기화. 다섯 개의 티켓 이벤트를 수신하는 코디네이터가 로컬 티켓 변경을 구성된 공급업체로 밀어냅니다.

이벤트는 제품 표면 전체에 걸쳐 있습니다. 크게 보면 다음과 같습니다.

  • 워크스페이스, 에이전트, 저장소 — 워크스페이스 생성, 실행 완료, 저장소 등록, 스킬 업데이트
  • 풀 리퀘스트 — 게시, 상태 변경, 병합, 리뷰 요청, 멘션, 작성한 PR 감시, 외부에서 감지된 PR
  • 메시징 — 메시지, 스페이스, 스페이스 프로비저닝 진행 상황
  • 티켓 — 생성, 할당, 재할당, 세부 정보, 상태, 그리고 세 가지 최종 결과
  • 작업 — 디스패치된 한 번의 실행에 대한 순차적 수명주기 프레임으로, 클라이언트가 순서를 정하고 중복을 제거할 수 있습니다
  • 파이프라인 — 최종 실행 이벤트만(완료, 취소, 실패)
  • 관측성 — 활동 로그 항목과 예산 임계값
  • ID와 멤버십 — 멤버 추가, 제거, 역할 변경
  • 캘린더와 회의 — 인증 만료, 회의 시작, 녹음 완료
  • 리그 — 인수, 회수, 예기치 않은 종료

메모리, 오케스트레이션, 플랜 문서, 아티팩트, 승인, 티켓 동기화 수신, 캘린더 새로 고침, 워크트리 병합, 사용자 생성, 초대, 기기 해지는 도메인 이벤트가 아닙니다. 레퍼런스를 참고하십시오.

전체 목록, 즉 53개의 구체적인 이벤트 클래스와 그 페이로드, 그리고 이를 소비하는 24개의 구독자는 도메인 이벤트 레퍼런스를 참고하십시오. 그 페이지는 목록이고, 이 페이지는 모델입니다.

실시간 액세스 제어는 서로 다른 두 메커니즘 위에서 동작합니다

섹션 제목: “실시간 액세스 제어는 서로 다른 두 메커니즘 위에서 동작합니다”

멤버십 이벤트와 기기 이벤트는 대칭적으로 보이지만 그렇지 않으며, 해지가 얼마나 빨리 적용되는지 따져 볼 때 이 차이가 중요합니다.

기기를 해지하면 실제로 그 세션이 종료됩니다. 서버는 페어링된 기기 테이블을 감시하다가 활성 집합에서 벗어난 기기의 살아 있는 세션을 몇 초 안에 끊습니다.

멤버를 제거해도 소켓은 닫히지 않습니다. 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를 구동하기 위해 실시간으로 전달되지만, 알림 피드에 기록되지 않고 토스트도 띄우지 않습니다.