Hari ini, kami memiliki sekitar 1,1 juta baris kode TypeScript yang telah dikomitmen di monorepo kami. Skalabilitasnya membawa berbagai tantangan, seperti pengecekan tipe yang lambat, impor yang membengkak, dan traceability kode yang semakin buruk. Selama beberapa tahun terakhir, kami telah mengeksplorasi dan menguji berbagai pola desain untuk meningkatkan skalabilitas basis kode kami. Salah satu pola yang kami temukan membantu meredakan banyak masalah organisasi kode yang semakin kompleks: Registries. Pola ini sederhana dan merupakan salah satu pola paling skalabel yang kami adopsi.
Kode penanganan acara internal asli kami merupakan contoh yang baik tentang bagaimana pola ini membuat perbedaan yang signifikan. Awalnya sederhana, tetapi seiring waktu, hal ini menyebabkan union tipe yang besar yang mengakibatkan pemeriksaan tipe yang lambat, berkas barrel yang mengimpor terlalu banyak kode, dan kode yang sulit dilacak akibat penggabungan string yang berlebihan. Terakhir, hal ini menimbulkan gesekan bagi pengembang dalam memelihara dan menambahkan penangan acara baru.
Desain asli
Secara umum, layanan acara kami dibangun di atas antrian pesan (MQ). Acara-acara direkam dan dikirim ke MQ dari mana saja di backend kami (pod API, pod pekerja, dll.) dan diproses oleh pekerja tepat sekali.
Desainnya sendiri sangat sederhana; satu-satunya batasan utama adalah bahwa semua peristiwa harus sepenuhnya dapat diserialisasikan melalui jaringan. Sebagian besar kompleksitas dan pemeliharaan berasal dari keamanan tipe dan pengalaman pengembang.
1. Tentukan sekumpulan fungsi bantu dasar:
2. Tentukan jenis acara dalam berkas bersama
3. Tentukan penangan acara (event handlers) dalam berkas tersendiri.
4. Tentukan berkas rollup
5. Tentukan penangan acara
6. Tentukan fungsi umum untuk mencatat peristiwa.
7. Ekspor fungsi yang memproses penangan acara untuk antrian di pekerja.
Secara sekilas, desain ini terlihat cukup baik. Desain ini aman tipe, mudah dipahami, sederhana untuk digunakan, dan jelas tentang cara membuat acara baru. Namun, ada beberapa masalah:
Kompleksitas pengecekan tipe: Untuk setiap acara baru, Acara Aplikasi Tipe menjadi lebih besar. Secara individual, satu union tidak menjadi masalah, tetapi seiring dengan meningkatnya penggunaan pola ini di seluruh basis kode (beberapa union tipe), kinerja pengecekan tipe menurun dengan cepat.
Pemuatan modul yang antusias: Pola file rollup kami menyebabkan hampir setiap modul mengimpor seluruh basis kode saat startup, sehingga memuat secara malas (lazy-loading) menjadi tidak mungkin. Hal ini juga menghalangi kami untuk dengan mudah membagi server menjadi bundel terpisah untuk deployment yang berbeda. Meskipun memuat seluruh basis kode secara langsung (eager-loading) dapat diterima di lingkungan produksi, hal ini sangat mempengaruhi pengembangan dan pengujian, dengan waktu startup server pengembangan mencapai lebih dari 20–30 detik.
Kurangnya jejak yang dapat dilacak: Menjawab pertanyaan sederhana seperti "di mana implementasi acara ini?" atau "di mana penangan ini dipanggil?" sangat sulit. Kami harus mengandalkan pencarian teks lengkap pada literal string. Misalnya, jika seorang insinyur memutuskan untuk melakukan hal seperti ini:
Sangat sulit untuk melacak bahwa kabel_terkirim dan kabel_dibuat Acara-acara dipicu dari sini. Mencari string "wire_sent" dalam kode sumber tidak akan mengungkapkan penggunaan ini, karena nama tersebut dibentuk secara dinamis. Akibatnya, informasi ini menjadi pengetahuan "tribal" yang tersembunyi dan hanya diketahui oleh sejumlah kecil insinyur terpilih.
Ketiadaan batas domain yang jelas: Seiring dengan pengenalan antarmuka yang lebih homogen (penanganan peristiwa, entitas basis data, pemeriksaan kesehatan), hal ini mendorong penempatan antarmuka serupa dalam folder yang sama daripada mengelompokkannya berdasarkan logika domain. Hal ini menyebabkan fragmentasi logika bisnis kami, sehingga perpindahan konteks yang spesifik domain menjadi lebih sering dan kompleks.
Mengembangkan desain ini secara berulang
Dalam konfigurasi di atas, kami menempatkan semua penangan acara di ./app-event-handlers/<event_type>.ts</event_type> Berkas-berkas. Meskipun menyimpan semua berkas dalam satu folder memudahkan pencarian, hal itu tidak mencerminkan cara kerja kami yang sebenarnya. Dalam praktiknya, menempatkan pemroses acara (event handlers) bersama dengan logika aplikasi yang relevan lainnya terbukti jauh lebih berguna daripada mengelompokkannya dengan pemroses acara lainnya.
Itulah ide untuk menambahkan subekstensi ke file (.event-handler.ts) masuk. Mereka mengizinkan kami untuk menempatkan server secara bersama-sama berdasarkan domain sambil tetap memudahkan penemuan dengan mencari ekstensi file. Ekstensi file tersebut juga memungkinkan kami untuk menghapus file rollup yang dikelola secara manual, karena kami dapat memindai semua file yang sesuai dengan ekstensi tersebut di repositori pada saat runtime.
Berikut ini adalah versi ringkas dari kode registri dasar dan cara kerjanya. memuat modul akan memindai semua file dan mendaftarkan semua objek yang diekspor dengan sebuah $diskriminator properti yang sesuai dengan simbol yang sama yang dimasukkan ke dalam Buat Registri.
Sekarang, berikut ini adalah cara membuat event handler menggunakan Registries:
1. Tentukan sebuah registri dalam sebuah <name>.registry.ts</name> berkas:
2. Tentukan penangan acara yang sebenarnya dalam .app-event-handler.ts berkas
3. Tentukan fungsi umum untuk mencatat peristiwa
4. Ekspor fungsi yang memproses penangan acara untuk antrian di pekerja.
Beberapa perbedaan penting yang perlu diperhatikan:
Traceabilitas kode jauh lebih baik.Setiap kali Anda merekam suatu peristiwa, Anda merekamnya seperti ini:
Ini berarti bahwa mudah untuk melacak semua tempat di mana Penerima Acara Pembuatan Kartu digunakan dengan menggunakan alat AST seperti "Temukan semua referensi" (di VS Code). Sebaliknya, ketika Anda melihat sebuah catatAcara Anda dapat mengklik "Buka implementasi" untuk menemukan definisi acara dan penanganannya.
Tidak ada lagi serikat pekerjaSebaliknya, kami menggunakan tipe dasar, yang merupakan sesuatu yang TypeScript Mendorong untuk menghindari masalah kinerja yang disebabkan oleh pengecekan tipe pada union besar.
Penanganan peristiwa ditempatkan bersama dengan logika spesifik domain.Penanganan acara aplikasi tidak lagi disimpan dalam satu folder. Sebaliknya, mereka ditempatkan bersama dengan logika bisnis yang relevan. Misalnya, layanan khusus domain mungkin terlihat seperti ini:
Bekerja dengan Registri saat ini
Hari ini, kami bekerja sama dengan puluhan registri untuk memastikan semua kode disimpan bersama dengan logika aplikasi mereka. Beberapa di antaranya yang menonjol meliputi:
.db.tsuntuk mendaftarkan entitas basis data.workflows.tsdan.aktivitas.tsuntuk mendaftar Waktu alur kerja.checks.tsuntuk mendaftarkan pemeriksaan kesehatan (artikel blog).utama.tsuntuk mendaftarkan layanan yang mengelompokkan logika bisnis spesifik domain.permission-role.tsdan.permission-key.tsuntuk mendefinisikan izin RBAC dalam produk kami.kotak-email.tsUntuk mendaftarkan penangan yang memproses email di akun Gmail..cron.tsuntuk mendaftarkan tugas cron.saldo-buku-besar.tsuntuk mendefinisikan primitif "buku besar" keuangan internal kamimetrik.tsuntuk mendefinisikan metrik Datadog
dan beberapa ekstensi khusus domain lainnya.
Saat ini, kami belum membuka sumber kode pola ini, tetapi semoga posting ini dapat memberikan gambaran yang jelas tentang cara mengimplementasikannya di kode sumber lain. Jika Anda menemukan ini bermanfaat, cobalah mengimplementasikannya di proyek Anda sendiri dan beritahu kami bagaimana hasilnya!