Lewati ke konten

Peristiwa domain

Peristiwa domain adalah mekanisme pemisahan yang memungkinkan fitur-fitur berkomunikasi tanpa ketergantungan langsung. Alih-alih fitur A memanggil fitur B, fitur A menerbitkan sebuah peristiwa dan fitur B berlangganan padanya.

DomainEventBus adalah bus publish/subscribe siaran dalam proses. Penerbit memanggil publish(event); pelanggan mengonsumsi aliran on<T>() yang bertipe. Setiap peristiwa mengimplementasikan DomainEvent dan membawa occurredAt.

Bus ini berjalan di dalam cc_server. Setiap penerbit dan setiap pelanggan yang aktif berada di sana. Tidak ada klien yang melihat bus ini. Yang dilihat klien adalah subset terkurasi yang dikirim ulang server sebagai frame JSON-RPC notifications/*.

Tanpa peristiwa, setiap reaksi antarfitur adalah sebuah impor. Jalur notifikasi harus mengimpor fitur agen, fitur pull request, fitur tiket, dan rapat hanya untuk mengetahui kapan harus memunculkan toast — dan masing-masing dari mereka kemudian harus tahu bahwa notifikasi itu ada. Fitur tiket harus mengimpor pipeline agar tiket yang selesai dapat memajukan sebuah run. Graf ketergantungan menutup menjadi sebuah simpul, dan kernel bersama berhenti menjadi bersama.

Menerbitkan peristiwa memutus hal itu. Jalur notifikasi berlangganan dua puluh satu kelas peristiwa dan tidak mengimpor satu pun fitur yang memunculkannya. Dispatcher pemicu pipeline berlangganan seluruh bus dan tidak tahu apa-apa tentang pull request, tiket, atau rapat. Observabilitas mengagregasi hasil run tanpa menjangkau sampai ke dispatch.

Biayanya adalah yang biasa: penerbit tidak dapat mengetahui apakah ada yang mendengarkan, dan pendengar yang tidak pernah dikonstruksi akan pasif secara diam-diam. TicketRemoteSyncHandler adalah contoh tetapnya: ia dideklarasikan, dicakup oleh pengujian, dan tidak pernah dirangkai di produksi.

Ada baiknya kita tepat mengenai satu hal yang tidak dimiliki oleh bus ini.

Jejak audit bukanlah proyeksi dari aliran peristiwa. Setiap operasi RPC yang memutasi diaudit secara default — RepoOp.audited bernilai true secara default dan sebuah operasi hanya menolaknya dengan mendeklarasikan audited: false, tindakan sengaja yang terlihat pada operasi itu sendiri. Dispatcher menambahkan baris “siapa melakukan apa, dari mana” tepat setelah panggilan berhasil. Tidak ada DomainEventAuditBridge. ActivityLogPersister menulis baris activity_log dari peristiwa ActivityLogged; itu adalah feed yang terpisah dari audit RPC.

Bus peristiwa adalah transport untuk notifikasi dan untuk otomatisasi. Ia bukan sistem pembukuan yang menjadi catatan resmi.

  • Notifikasi. Dua puluh satu kelas peristiwa diubah menjadi frame notifications/* oleh server, direkam ke dalam feed per ruang kerja yang tahan lama dan disiarkan ke sesi yang terhubung. Frame yang workspace_id-nya menyebut ruang kerja yang pengguna sesinya bukan anggotanya akan dibuang di server; klien kemudian memfilter sisanya ke ruang kerja yang sedang dilihatnya.
  • Otomatisasi berbasis peristiwa. Pemicu pipeline berlangganan bus dan memulai otomatis pipeline yang cocok (lihat di bawah).
  • Reaksi siklus hidup. Membuat ruang kerja akan menyemai agen CEO dan agen spesialisnya, templat pipeline bawaan, dan suite eval pemula. Run agen yang selesai melanjutkan langkah pipeline yang dijeda, menutup tugas, memberi masukan ke supervisor tujuan, dapat membangunkan pemeriksa bernama, dan dapat memicu ulang pemimpin tim. PR yang digabungkan atau space yang dihapus memicu pengumpulan sampah worktree.
  • Sinkronisasi lintas vendor. Perubahan tiket lokal didorong keluar ke vendor yang dikonfigurasi oleh koordinator yang mendengarkan lima peristiwa tiket.

Peristiwa mencakup seluruh permukaan produk. Secara umum:

  • Ruang kerja, agen, dan repo — pembuatan ruang kerja, penyelesaian run, pendaftaran repo, pembaruan skill
  • Pull request — penerbitan, perubahan status, penggabungan, permintaan tinjauan, penyebutan, pemantauan PR yang Anda tulis, PR yang terdeteksi dari luar
  • Pesan — pesan, space, dan kemajuan penyediaan space
  • Tiket — pembuatan, penugasan, penugasan ulang, detail, status, dan tiga hasil akhir
  • Tugas — frame siklus hidup berurutan untuk satu run yang dikirim, agar klien dapat mengurutkan dan menghilangkan duplikatnya
  • Pipeline — hanya peristiwa run terminal (selesai, dibatalkan, gagal)
  • Observabilitas — entri log aktivitas dan ambang batas anggaran
  • Identitas dan keanggotaan — anggota ditambahkan, dihapus, peran diubah
  • Kalender dan rapat — kedaluwarsa autentikasi, rapat dimulai dan perekaman selesai
  • Rig — pengambilalihan, reap, dan penutupan tak terduga

Memori, orkestrasi, dokumen rencana, artefak, persetujuan, penerimaan sinkronisasi tiket, penyegaran kalender, penggabungan worktree, pembuatan pengguna, undangan, dan pencabutan perangkat bukanlah peristiwa domain; lihat referensi.

Untuk katalog lengkapnya — seluruh 53 kelas peristiwa konkret, payload-nya, dan 24 pelanggan yang mengonsumsinya — lihat Referensi peristiwa domain. Halaman itu adalah inventarisnya; halaman ini adalah modelnya.

Kontrol akses langsung menempuh dua mekanisme berbeda

Bagian berjudul “Kontrol akses langsung menempuh dua mekanisme berbeda”

Peristiwa keanggotaan dan perangkat terlihat simetris, padahal tidak, dan hal ini penting jika Anda bernalar tentang seberapa cepat pencabutan berlaku.

Mencabut perangkat memang mengakhiri sesinya. Server mengawasi tabel perangkat yang dipasangkan dan menjatuhkan setiap sesi aktif yang perangkatnya keluar dari himpunan aktif, dalam hitungan detik.

Menghapus anggota tidak menutup soket. WorkspaceMemberRemoved dikonsumsi: LocalRpcServer dan RemoteRelayHost menjatuhkan langganan ruang kerja pengguna tersebut yang terpasang, dan RemoteEventForwarder membatalkan cache keanggotaannya sehingga frame berikutnya untuk ruang kerja itu tidak lagi didorong. Yang menolak RPC berikutnya tetap gerbang peran, yang menyelesaikan ulang keanggotaan pada setiap panggilan — sehingga permintaan berikutnya ditolak dengan “Not a member of this workspace”. Efeknya langsung terasa untuk apa pun yang mereka coba lakukan; hanya saja efek itu tidak dicapai dengan memutus koneksi mereka.

Pemicu pipeline berlangganan peristiwa domain dan memulai otomatis pipeline yang cocok. PipelineTriggerDispatcher mendengarkan seluruh bus, lalu:

  1. Memutus lebih awal kecuali tipe peristiwa tersebut termasuk yang dapat dipetakannya ke payload
  2. Mencari pemicu yang aktif yang cocok dengan tipe peristiwa itu, di setiap ruang kerja
  3. Memfilter masing-masing ke ruang kerjanya sendiri dan menerapkan filter kecocokan payload-nya
  4. Memulai run untuk setiap pemicu yang bertahan

Batasan pentingnya adalah langkah 1. Bus membawa 53 tipe peristiwa; pemilih tambah pemicu memulai run untuk empat belas ini (cabang payload EventPayloadMapper): ExternalPrDetected, PullRequestPublished, PullRequestStatusChanged, PrMerged, MessageReceived, TicketAssigned, TicketCompleted, TicketFailed, TicketCancelled, BudgetThresholdCrossed, RepoAdded, MeetingRecordingStopped, SkillUpdated dan SpaceDeleted. Tidak ada yang lain yang dapat memulai pipeline.

RemoteEventForwarder menerjemahkan peristiwa kelas notifikasi menjadi frame dan mendorongnya melalui RPC ke sesi yang terhubung, di mana pemeta frame mengubahnya menjadi notifikasi dalam aplikasi. Klien tipis karenanya melihat proyeksi dari aliran peristiwa server tanpa memiliki eksekusi apa pun — itulah inti dari pemisahan klien tipis.

Namun, tidak setiap frame yang didorong adalah notifikasi. Aliran siklus hidup tugas dan penugasan ulang tiket diteruskan secara langsung untuk menggerakkan UI, tetapi keduanya tidak direkam ke dalam feed notifikasi dan tidak memunculkan toast.