âš¡ Jawaban Singkat / Key Takeaways
Migrasi codebase legacy besar tidak perlu rewrite total. Pola Strangler Fig dengan strategi leaf module first plus feature flags memungkinkan penggantian komponen C++ secara inkremental. Risiko downtime mendekati nol karena sistem lama dan baru berjalan paralel lewat interop layer. Kamu bisa mulai besok tanpa rewrite 300 ribu baris kode sekaligus.
Kamu Kenal Perasaan Ini: Satu Line Error, Seluruh Sistem Mati
Senin pagi, jam 09:23. VP Engineering masuk ke ruang standup dengan wajah tegang. “Tim infrastructure bilang server payment gateway C++ kita sudah tidak bisa di-build di compiler versi baru. Upgrade compiler butuh rewrite 40% codebase. Deadline compliance PCI DSS tiga bulan lagi.”
Kamu sudah menghitung di kepala: 300 ribu baris kode C++98, dokumentasi nyaris nol, tiga engineer senior yang paham sudah resign dua tahun lalu, dan test coverage cuma 12 persen. Refleks pertama semua orang: rewrite total. Tapi itu refleks yang salah.
Ini bukan cerita fiksi. Martin Fowler mendokumentasikan pola yang sama sejak 2004, dan Microsoft Azure Architecture Center mengadopsinya sebagai best practice. Polanya sederhana: jangan ganti semua. Ganti satu per satu. Biarkan sistem lama dan baru hidup berdampingan.
Kenapa Big Bang Rewrite Adalah Bunuh Diri Teknis
Setiap engineering manager pernah tergoda: “Kita rewrite semua pakai Rust, selesai dalam enam bulan, bersih.” Realitanya? Data dari Standish Group CHAOS Report menunjukkan proyek rewrite total punya tingkat kegagalan dua kali lipat dibanding proyek incremental improvement. Alasannya bukan karena skill engineer rendah. Tapi karena legacy code mengandung business logic bertahun-tahun yang tidak terdokumentasi.
Bayangkan kamu rewrite sistem billing. Di tengah jalan, kamu sadar ada aturan diskon spesial untuk customer enterprise yang cuma berlaku di bulan Desember, yang cuma ada di satu fungsi dengan nama variabel tmp123_old. Tidak ada yang tahu kenapa aturan itu ada. Tapi kalau hilang, finance langsung melapor selisih revenue. Inilah kenapa rewrite total hampir selalu molor 3x dari estimasi awal.
Baca juga pengalaman kami menghadapi dilema serupa di artikel kapan harus rewrite Python service ke Rust dan analisis arsitektur monolit vs microservice.

Pola Strangler Fig: Cara Aman Ganti Komponen Satu per Satu
Nama Strangler Fig diambil dari pohon ara yang tumbuh di atas pohon inang. Ia tidak langsung membunuh inangnya. Ia tumbuh perlahan, akarnya menjalar ke bawah, sampai akhirnya menggantikan seluruh struktur pohon lama. Begitu juga migrasi codebase: komponen baru tumbuh di samping legacy, mengambil alih fungsionalitas sedikit demi sedikit, sampai legacy bisa dimatikan tanpa efek samping.
Implementasi konkretnya dimulai dengan tiga langkah:
- Routing proxy: Pasang reverse proxy (Nginx, Envoy, atau Traefik) di depan sistem. Route traffic ke komponen baru hanya untuk endpoint yang sudah di-migrasi. Sisanya tetap ke legacy.
- Interop layer: Bangun jembatan komunikasi antara modul Rust baru dengan modul C++ lama. Ini bisa lewat FFI (Foreign Function Interface), shared memory, gRPC, atau message queue.
- Dual-write phase: Tulis data ke dua sistem sekaligus (legacy dan baru). Baca dari legacy dulu. Setelah validasi konsisten, switch baca ke sistem baru.
Pola ini sudah dipakai oleh AWS dalam refactoring monolith internal mereka. Hasilnya: nol downtime, rollback instan, dan tim engineer bisa deliver value sambil migrasi.
Leaf Module First: Kenapa Kamu Harus Mulai dari Ujung
Salah satu kesalahan paling mahal dalam migrasi adalah mulai dari core module. “Kita ganti dulu module database connector-nya,” kata seseorang di meeting. Dua bulan kemudian, seluruh sistem tidak bisa di-test karena konektor baru belum stabil. Ini mematikan semua development velocity tim lain.
Strategi leaf module first membalik logika itu. Mulai dari modul yang paling sedikit dependensinya. Modul yang kalau error, cuma satu fitur kecil yang terpengaruh. Contoh di sistem C++ besar:
- Utility functions: String parser, date formatter, CSV exporter. Ini leaf module paling ujung. Tidak ada yang depend ke mereka selain consumer langsung.
- Notification worker: Kirim email atau push notification. Kalau error, notifikasi terlambat tapi bisnis utama tetap jalan.
- Logging/metrics exporter: Ganti dengan implementasi baru. Kalau salah, monitoring-mu mungkin kosong sejam, tapi transaksi tetap diproses.
Setelah leaf module stabil, kamu naik satu level ke module yang sedikit lebih sentral. Pola ini seperti mengupas bawang dari lapisan terluar. Setiap lapisan yang berhasil diganti memberi confidence untuk lapisan berikutnya.

Feature Flags: Jaring Pengaman yang Memungkinkan Rollback Instan
Feature flag bukan sekadar if-else statement. Di konteks migrasi legacy, feature flag adalah jaring pengaman produksi. Setiap komponen baru yang kamu deploy harus dibungkus dengan feature flag yang bisa dimatikan kapan saja, bahkan di production, tanpa deploy ulang.
Arsitektur flag yang efektif untuk migrasi C++ punya tiga level:
- Build-time flag: Gunakan preprocessor directive (
#ifdef USE_NEW_PARSER) untuk compile dua versi. Berguna saat uji coba awal di staging. - Runtime flag: Baca konfigurasi dari file JSON, environment variable, atau service seperti LaunchDarkly. Bisa diubah tanpa recompile. Cocok untuk canary deployment.
- Context-aware flag: Flag yang nilainya bergantung pada konteks request (user ID, tenant, region). Ini memungkinkan kamu expose komponen baru hanya ke 1 persen user internal dulu.
Yang menarik, dan ini insight yang jarang dibahas: feature flag di migrasi legacy justru paling berguna bukan saat komponen baru bekerja, tapi saat komponen baru gagal. Satu kali switch off di production yang menyelamatkan jam 3 pagi itu sudah membayar seluruh investasi infrastruktur flag.
Interop Layer: Jembatan Dua Dunia Tanpa UB
Komponen baru (misalnya ditulis dalam Rust atau Go) harus bisa bicara dengan komponen C++ lama. Tanpa interop layer yang solid, boundary antara dua runtime adalah hotspot bug memory safety. Double-free, use-after-free, dan buffer overflow sering muncul di sini, tepat di jembatan dua bahasa.
Pendekatan yang paling aman adalah zero-copy FFI dengan verifikasi ownership. Jika kamu memakai Rust untuk komponen baru, pastikan setiap pointer yang melewati boundary punya kontrak eksplisit: siapa yang punya, siapa yang pinjam, dan kapan data boleh di-drop. Kami sudah membahas ini secara mendalam di artikel FFI Rust-C++ dan Safe C++.
Alternatif lain kalau FFI terlalu rawan: gunakan message passing lewat shared memory atau gRPC. Overhead latency memang lebih tinggi (biasanya 50-200 mikrodetik tambahan), tapi kontrak data jadi eksplisit lewat protobuf atau flatbuffer schema. Untuk 90 persen kasus migrasi bisnis, overhead ini tidak signifikan dibanding risiko memory corruption.

Checklist Migrasi 5 Fase: Dari Rencana ke Production
Berikut playbook yang bisa kamu eksekusi mulai sprint depan. Setiap fase punya exit criteria yang jelas sebelum lanjut ke fase berikutnya:
- Dependency mapping (1-2 minggu): Generate dependency graph codebase pakai tool seperti
cinclude2dot(C++),cargo-modules(Rust), ataumadge(Node.js/C++ hybrid). Tandai leaf module yang tidak punya dependents. Exit criteria: kamu bisa melihat semua modul yang aman diganti duluan. - Interop scaffolding (2-4 minggu): Bangun jembatan komunikasi antara runtime lama dan baru. Pilih pendekatan: FFI direct atau message passing. Tulis integration test untuk boundary ini. Exit criteria: satu modul dummy bisa berkomunikasi antar runtime.
- Leaf module replacement (per module: 1-3 minggu): Ganti satu leaf module. Bungkus dengan feature flag. Deploy ke production dengan traffic 0 persen dulu, lalu naikkan bertahap. Exit criteria: modul baru jalan di 100 persen traffic selama 2 minggu tanpa insiden.
- Mid-tier migration (3-6 bulan): Setelah semua leaf module terganti, naik ke module yang lebih sentral. Pantau terus P99 latency dan error rate. Exit criteria: 50 persen atau lebih fungsionalitas bisnis sudah jalan di komponen baru.
- Legacy decomission (1-2 bulan setelah semua terganti): Matikan routing ke legacy. Biarkan dulu 2 minggu dalam mode “zombie” (tidak menerima traffic tapi masih bisa dihidupkan). Kalau tidak ada masalah, hapus kode legacy. Rayakan dengan tim.
FAQ: Migrasi Bertahap Codebase Legacy
Apa bedanya Strangler Fig Pattern dengan rewrite total?
Strangler Fig mengganti komponen secara bertahap sementara sistem tetap beroperasi. Komponen lama dan baru berjalan paralel. Rewrite total membangun ulang seluruh sistem dari nol secara terpisah, lalu melakukan cutover besar sekaligus. Strangler Fig jauh lebih aman karena setiap langkah bisa di-rollback tanpa dampak bisnis.
Bagaimana menentukan modul mana yang harus diganti duluan?
Gunakan dependency graph analysis. Modul yang paling aman diganti duluan adalah leaf module: modul yang tidak punya modul lain bergantung padanya. Utility functions, formatter, exporter, dan notification worker biasanya adalah leaf. Modul core seperti database connector atau authentication service diganti paling akhir.
Apakah feature flag menambah overhead performa?
Overhead feature flag sangat minimal karena biasanya berupa satu kali pengecekan boolean atau lookup konfigurasi yang di-cache. Dalam konteks C++ khususnya, build-time flag dengan preprocessor directive (#ifdef) justru nol overhead runtime karena diputuskan saat kompilasi. Untuk runtime flag, overhead-nya berkisar 10-50 mikrodetik per evaluasi, yang bisa diabaikan untuk hampir semua use case bisnis.
Berapa lama waktu yang realistis untuk migrasi codebase 300 ribu baris?
Dengan strategi leaf module first dan tim 4-6 engineer, migrasi 300 ribu baris umumnya memakan waktu 12-18 bulan. Ini jauh lebih cepat dibanding rewrite total yang biasanya 24-36 bulan dengan risiko kegagalan tinggi. Kuncinya: value bisnis tetap ter-deliver selama migrasi karena sistem lama terus beroperasi.
Kesimpulan: Kamu Tidak Perlu Pahlawan, Kamu Perlu Proses
Momentum terbesar pembunuh codebase legacy bukanlah kurangnya talenta atau budget. Momentum itu adalah ketakutan mengambil langkah pertama. “Nanti aja, kita butuh waktu 6 bulan untuk rencana sempurna.” Sementara itu, codebase terus membusuk. Compiler terus update. Dependency terus deprecated.
Strategi di artikel ini dirancang untuk menghilangkan alasan itu. Kamu tidak perlu rencana sempurna. Kamu cuma perlu dependency graph, satu leaf module, satu feature flag, dan satu sprint. Sisanya akan mengikuti ritme sendiri. Sebulan pertama mungkin cuma satu modul kecil. Tapi bulan keenam, ritme itu berubah jadi mesin yang menggerakkan seluruh organisasi.
Kalau kamu sedang atau akan memulai migrasi codebase di organisasi kamu, share tantangan terbesarmu di kolom komentar. Atau kalau kamu sudah berhasil menerapkan pola ini, ceritakan pengalamanmu. Komunitas engineering Indonesia butuh lebih banyak cerita nyata tentang migrasi yang berhasil.



