Strategi Event Versioning di Sistem Event-Driven: Menjaga Kompatibilitas Saat Perubahan Tak Terhindarkan
Di dunia pengembangan perangkat lunak modern, Arsitektur Event-Driven (EDA) telah menjadi pilihan populer untuk membangun sistem yang responsif, skalabel, dan terdistribusi. Dengan EDA, berbagai layanan berkomunikasi satu sama lain melalui event, yaitu fakta-fakta yang terjadi di sistem Anda.
Namun, seiring waktu, kebutuhan bisnis akan berubah. Anda mungkin perlu menambahkan informasi baru ke event, mengubah struktur data yang ada, atau bahkan menghapus field yang tidak lagi relevan. Di sinilah tantangan besar muncul: bagaimana kita bisa melakukan perubahan pada skema event tanpa merusak layanan lain yang mengonsumsi event tersebut?
Inilah inti dari Event Versioning. Ini adalah strategi vital untuk mengelola evolusi skema event Anda, memastikan sistem tetap berjalan mulus meskipun ada perubahan. Artikel ini akan membawa Anda menyelami berbagai strategi praktis untuk mengimplementasikan event versioning, menjaga kompatibilitas, dan menghindari drama di sistem event-driven Anda.
1. Pendahuluan: Kenapa Event Versioning Itu Penting?
Bayangkan Anda memiliki aplikasi e-commerce. Ketika seorang pengguna baru mendaftar, layanan UserService mungkin akan memancarkan event UserRegistered. Event ini berisi informasi dasar seperti userId, email, dan registrationDate.
// Event: UserRegistered_v1
{
"userId": "usr_123",
"email": "john.doe@example.com",
"registrationDate": "2023-10-26T10:00:00Z"
}
Event ini kemudian dikonsumsi oleh berbagai layanan lain:
EmailServiceuntuk mengirim email selamat datang.AnalyticsServiceuntuk mencatat metrik pendaftaran.LoyaltyServiceuntuk memberikan poin awal.
Beberapa bulan kemudian, tim pemasaran memutuskan bahwa mereka juga ingin melacak sumber pendaftaran (misalnya, dari mana pengguna menemukan aplikasi Anda). Mereka meminta Anda untuk menambahkan field referralSource ke event UserRegistered.
Jika Anda langsung mengubah skema event tersebut menjadi:
// Event: UserRegistered_v2
{
"userId": "usr_123",
"email": "john.doe@example.com",
"registrationDate": "2023-10-26T10:00:00Z",
"referralSource": "Google Ads" // Field baru
}
…apa yang akan terjadi pada EmailService, AnalyticsService, dan LoyaltyService yang masih mengharapkan skema UserRegistered_v1? ⚠️ Kemungkinan besar, mereka akan error!
Inilah masalah yang dipecahkan oleh event versioning. Ini adalah cara kita untuk memastikan bahwa:
- Kompatibilitas Mundur (Backward Compatibility): Konsumen lama masih bisa memproses event baru.
- Kompatibilitas Maju (Forward Compatibility): Konsumen baru masih bisa memproses event lama.
Tanpa strategi versioning yang jelas, evolusi sistem event-driven Anda akan menjadi mimpi buruk yang penuh dengan breaking changes dan downtime.
2. Mengapa Event Berubah? Keniscayaan dalam Sistem Modern
Perubahan adalah satu-satunya hal yang konstan dalam pengembangan perangkat lunak. Event, sebagai representasi fakta bisnis, tidak luput dari dinamika ini. Beberapa alasan umum mengapa event Anda perlu berubah:
- Evolusi Kebutuhan Bisnis: Penambahan fitur baru, perubahan alur bisnis, atau kebutuhan data analitik yang lebih kaya seringkali memerlukan event untuk membawa informasi lebih banyak atau berbeda.
- Perbaikan Bug: Terkadang, event awal mungkin memiliki desain yang kurang tepat atau mengandung bug yang perlu diperbaiki, yang mungkin memengaruhi struktur datanya.
- Refactoring Internal: Meskipun event seharusnya menjadi kontrak yang stabil, terkadang perubahan internal pada layanan produsen atau konsumen bisa memicu kebutuhan untuk menyelaraskan struktur event.
- Optimalisasi: Anda mungkin menemukan cara yang lebih efisien untuk merepresentasikan data atau mengurangi ukuran event untuk performa yang lebih baik.
Memahami bahwa event akan berubah adalah langkah pertama. Langkah selanjutnya adalah memiliki strategi yang solid untuk mengelola perubahan tersebut.
3. Prinsip Dasar Kompatibilitas Event
Sebelum masuk ke strategi, mari kita pahami dua pilar utama dalam event versioning:
✅ Kompatibilitas Mundur (Backward Compatibility)
Ini berarti konsumen lama harus bisa membaca dan memproses event yang dipancarkan oleh produsen versi baru.
Skenario:
- Produsen merilis
v2event. - Konsumen masih menggunakan logika
v1event. - Konsumen
v1tidak boleh error saat menerimav2event.
Contoh Praktis:
Jika Anda menambahkan field baru (referralSource) ke event UserRegistered, konsumen v1 harus bisa mengabaikan field ini tanpa error. Ini biasanya dicapai dengan membuat field baru bersifat opsional atau memberikan nilai default.
✅ Kompatibilitas Maju (Forward Compatibility)
Ini berarti konsumen baru harus bisa membaca dan memproses event yang dipancarkan oleh produsen versi lama.
Skenario:
- Konsumen merilis
v2event. - Produsen masih memancarkan
v1event (misalnya, karena belum di-deploy atau ada event lama di queue). - Konsumen
v2tidak boleh error saat menerimav1event.
Contoh Praktis:
Jika konsumen v2 mengharapkan field referralSource, dan menerima event UserRegistered_v1 yang tidak memiliki field tersebut, konsumen v2 harus bisa menangani kasus ini (misalnya, dengan menggunakan nilai default atau menandainya sebagai null).
🎯 Tujuan utama kita adalah mencapai kedua jenis kompatibilitas ini sebanyak mungkin untuk meminimalkan breaking changes dan memudahkan deployment.
4. Strategi Event Versioning Praktis
Ada beberapa pendekatan untuk mengelola versi event, masing-masing dengan kelebihan dan kekurangannya. Pemilihan strategi seringkali tergantung pada tingkat perubahan dan toleransi sistem Anda terhadap kompleksitas.
a. Versioning di Nama Event atau Topik (Major Versioning)
Ini adalah strategi paling sederhana namun paling kaku. Setiap kali ada perubahan skema yang tidak kompatibel mundur atau maju (breaking change), Anda membuat event baru dengan nama yang berbeda atau mempublikasikannya ke topik yang berbeda.
- Contoh:
- Event
UserRegistered_v1dipublikasikan ke topikuser-events-v1. - Ketika ada perubahan breaking, Anda membuat event baru
UserRegistered_v2yang dipublikasikan ke topikuser-events-v2.
- Event
// Produsen v1
producer.send("user-events-v1", new UserRegisteredV1(...));
// Produsen v2 (setelah breaking change)
producer.send("user-events-v2", new UserRegisteredV2(...));
Kapan Digunakan: Ketika perubahan skema sangat besar dan tidak mungkin diakomodasi dengan kompatibilitas (misalnya, mengubah tipe data fundamental, menghapus field wajib).
Kelebihan:
- Jelas dan Terisolasi: Setiap versi event dan konsumennya terisolasi sepenuhnya.
- Mudah Dipahami: Tidak ada ambiguitas tentang versi mana yang sedang diproses.
Kekurangan:
- Duplikasi Kode Konsumen: Konsumen perlu menulis ulang atau memodifikasi logika untuk setiap versi event baru.
- Migrasi yang Rumit: Anda perlu mengelola migrasi data (jika ada event lama yang perlu diproses ulang) dan migrasi konsumen dari
v1kev2. Ini seringkali membutuhkan periode transisi di mana kedua versi event dan konsumen berjalan bersamaan. - Banyak Topik: Bisa menyebabkan ledakan jumlah topik atau nama event.
b. Versioning Melalui Skema Event (Minor/Patch Versioning)
Ini adalah strategi yang paling umum untuk perubahan non-breaking yang menjaga kompatibilitas mundur dan maju. Anda mempertahankan nama event atau topik yang sama, tetapi memodifikasi skema event itu sendiri.
- Menambah Field Baru: Field baru harus selalu bersifat opsional atau memiliki nilai default.
- Produsen baru memancarkan event dengan field baru. Konsumen lama akan mengabaikannya (kompatibilitas mundur).
- Konsumen baru bisa membaca event lama (tanpa field baru) dengan menggunakan nilai default atau menangani kasus
null(kompatibilitas maju).
// Event v1
{ "userId": "usr_123", "email": "john.doe@example.com" }
// Event v2: menambah "referralSource" sebagai opsional
{ "userId": "usr_123", "email": "john.doe@example.com", "referralSource": "Google Ads" }
-
Menghapus Field: Field yang akan dihapus harus sudah bersifat opsional dan tidak digunakan oleh konsumen manapun.
- Produsen baru tidak lagi memancarkan field tersebut. Konsumen lama akan mencoba membacanya dan mendapatkan
nullatau nilai default (kompatibilitas mundur). - Konsumen baru tidak mengharapkan field tersebut (kompatibilitas maju).
- Produsen baru tidak lagi memancarkan field tersebut. Konsumen lama akan mencoba membacanya dan mendapatkan
-
Mengubah Nama Field: Ini adalah perubahan breaking jika tidak dihandle dengan hati-hati. Cara aman adalah:
- Tambahkan field baru dengan nama yang benar (opsional).
- Produsen memancarkan kedua field (lama dan baru) selama periode transisi.
- Konsumen dimigrasi untuk membaca field baru.
- Setelah semua konsumen migrasi, hapus field lama (jadikan opsional dulu, lalu hapus).
-
Mengubah Tipe Data Field: Ini hampir selalu merupakan perubahan breaking. Jika harus, pertimbangkan strategi major versioning (poin a).
Kapan Digunakan: Untuk sebagian besar perubahan inkremental yang tidak merusak kontrak fundamental event.
Kelebihan:
- Fleksibel: Memungkinkan evolusi event yang mulus.
- Tidak Perlu Migrasi Konsumen Segera: Konsumen tidak perlu di-deploy ulang setiap kali ada perubahan kecil.
Kekurangan:
- Membutuhkan Schema Registry: Untuk format seperti Avro atau Protocol Buffers, Schema Registry sangat membantu dalam menegakkan aturan kompatibilitas. Untuk JSON, Anda perlu validator skema dan disiplin tim.
- Hati-hati dengan Perubahan Tipe Data: Bisa menjadi jebakan jika tidak diatur dengan baik.
c. Envelope Pattern (Pembungkus Event)
Strateg