Menggunakan Contract Testing untuk Mengurangi Risiko Microservices

Microservices memberi kebebasan kepada tim untuk mengembangkan dan melakukan deployment service secara independen.

Masalahnya, kebebasan tersebut membawa risiko baru: satu perubahan API kecil bisa diam-diam merusak beberapa service lain yang bergantung padanya. Di sinilah Menggunakan Contract Testing menjadi strategi yang menarik.

Alih-alih selalu menyalakan seluruh sistem untuk mengecek integrasi, tim dapat memverifikasi apakah consumer dan provider masih memiliki pemahaman yang sama terhadap request, response, atau message yang dipertukarkan.

Hasilnya, breaking change dapat ditemukan jauh sebelum kode masuk production.

Apa Sebenarnya Contract Testing?

Contract testing adalah pengujian terhadap titik komunikasi antara dua aplikasi.

Misalnya Order Service membutuhkan Customer Service untuk memberikan response berisi customerId, name, dan membershipLevel.

Contract testing memastikan consumer memang mengirim request sesuai format yang disepakati dan provider benar-benar mampu memberikan response sesuai kebutuhan tersebut.

Pact mendefinisikannya sebagai teknik menguji integration point dengan memeriksa masing-masing aplikasi secara terisolasi, lalu memastikan message yang dikirim atau diterima sesuai dengan shared understanding yang terdokumentasi dalam contract.

Untuk komunikasi HTTP, contract berhubungan dengan request dan response. Pada sistem berbasis queue atau event, contract bisa merepresentasikan message yang dipublikasikan dan dikonsumsi.

Jadi, fokusnya bukan menguji seluruh sistem, melainkan menguji “kesepakatan” di antara dua service.

Kenapa Microservices Membutuhkan Contract Testing?

Pada monolith, perubahan method biasanya dapat ditemukan melalui compiler atau automated test dalam codebase yang sama.

Microservices berbeda.

Consumer dan provider mungkin mempunyai repository, release cycle, bahkan tim pengembang yang berbeda.

Misalnya Payment Service awalnya memberikan:

paymentStatus

Kemudian tim provider menggantinya menjadi:

status

Bagi provider, perubahan tersebut mungkin terlihat sederhana. Namun consumer yang masih membaca paymentStatus akan gagal setelah versi baru dirilis.

Contract test membuat dependency semacam ini eksplisit.

Martin Fowler menjelaskan bahwa integration contract test dapat memverifikasi apakah service memenuhi kontrak yang diharapkan consumer.

Ketika contract test milik consumer dijalankan dalam pipeline provider, tim provider bisa melihat dampak perubahan sebelum merilisnya.

Dengan begitu, kompatibilitas tidak lagi hanya bergantung pada dokumentasi API yang mudah terlupakan.

Memahami Consumer-Driven Contract

Salah satu pola populer adalah Consumer-Driven Contract atau CDC.

Consumer adalah aplikasi yang menggunakan suatu API, sedangkan provider adalah aplikasi yang menyediakan API tersebut.

Dalam pendekatan ini, kebutuhan consumer ikut menentukan contract.

Misalnya Checkout Service hanya menggunakan tiga field dari Product Service: id, price, dan availability.

Contract consumer cukup memastikan ketiga data tersebut tersedia.

Jika provider juga mempunyai field supplierCode, warehouseRegion, dan internalScore, consumer tidak perlu menguncinya jika memang tidak digunakan.

Pact menjelaskan bahwa salah satu keuntungan consumer-driven contract adalah hanya bagian komunikasi yang benar-benar digunakan consumer yang perlu diuji. Bagian provider yang tidak digunakan dapat berubah tanpa menyebabkan contract test gagal.

Pendekatan ini mengurangi coupling yang tidak diperlukan.

Contract menjadi representasi kebutuhan nyata, bukan seluruh struktur internal provider.

Bagaimana Alur Contract Testing Bekerja?

Implementasinya dapat berbeda tergantung tool, tetapi pola umumnya cukup sederhana.

Pada Pact, consumer menjalankan test terhadap mock provider. Dari interaksi tersebut dihasilkan sebuah contract yang mendokumentasikan request dan response yang diharapkan.

Contract kemudian dibagikan kepada provider.

Provider menjalankan verification menggunakan contract tersebut untuk memastikan implementasinya benar-benar memenuhi expectation consumer.

Dokumentasi Pact menjelaskan alur umum berupa consumer test, pembuatan contract, publikasi contract, kemudian provider verification terhadap request yang tersimpan. Dependency provider dapat di-stub agar pengujian tetap cepat dan deterministik.

Bayangkan Order Service membutuhkan:

GET /customers/123

dengan response yang memiliki id dan status.

Consumer test menghasilkan requirement tersebut.

Jika developer Customer Service secara tidak sengaja menghapus status, provider verification akan gagal.

Masalah ditemukan di pipeline, bukan setelah pengguna gagal melakukan checkout.

Jangan Mengubah Contract Test Menjadi Functional Test

Contract testing bukan tempat untuk menguji seluruh business logic provider.

Ini kesalahan yang cukup umum.

Misalnya provider mempunyai 50 aturan untuk menghitung limit kredit. Tidak perlu semua aturan tersebut dijalankan melalui contract test milik consumer.

Functional test seharusnya tetap berada di codebase provider.

Pact menekankan bahwa contract testing berfokus pada apakah consumer dan provider mempunyai pemahaman yang sama terhadap request serta response. Tujuannya bukan mencari semua bug internal provider.

Contohnya, contract dapat memastikan field creditLimit tersedia sebagai angka.

Namun apakah nilai kredit dihitung menggunakan rumus yang benar merupakan tanggung jawab unit atau component test provider.

Pemisahan ini penting agar contract suite tidak berubah menjadi integration test besar yang lambat dan susah dipelihara.

Gunakan Contract Test untuk Mengelola Breaking Change

Contract testing sangat berguna ketika API terus berevolusi.

Misalnya Customer Service ingin mengganti:

name

menjadi:

firstName dan lastName.

Daripada langsung menghapus field lama, provider bisa menambahkan field baru terlebih dahulu.

Contract verification kemudian menunjukkan consumer mana yang masih bergantung pada name.

Fowler menggambarkan bahwa kumpulan contract dari berbagai consumer dapat menunjukkan bagian interface mana yang benar-benar masih digunakan. Provider kemudian bisa melakukan perubahan bertahap tanpa memutus consumer secara mendadak.

Pola semacam ini cocok dipadukan dengan parallel change.

Tambahkan kontrak baru, migrasikan consumer, verifikasi semuanya, kemudian hapus kontrak lama setelah tidak ada consumer yang menggunakannya.

Perubahan API menjadi proses yang terukur, bukan sekadar pengumuman di grup chat yang mungkin terlewat.

Kurangi Ketergantungan pada End-to-End Environment

Tanpa contract testing, tim sering mencoba membuktikan integrasi dengan menyalakan hampir seluruh microservices.

Masalahnya, environment semacam ini mudah rapuh.

Satu service down dapat menggagalkan test service lain. Data harus sinkron. Deployment version harus cocok. Network dan infrastructure ikut memengaruhi hasil.

Pact menyebut contract testing sangat berguna dalam lingkungan dengan banyak service karena komunikasi dapat diverifikasi tanpa harus menjalankan semua aplikasi secara bersamaan.

Contract testing bukan berarti end-to-end test harus dihapus seluruhnya.

Tetap gunakan E2E untuk beberapa critical user journey seperti registrasi, checkout, atau pembayaran.

Namun sebagian besar kompatibilitas service-to-service dapat diverifikasi lebih dekat ke source code.

Hasilnya pipeline menjadi lebih cepat, kegagalan lebih mudah didiagnsis, dan tim tidak terlalu bergantung pada shared testing environment yang kompleks.

Integrasikan Contract Verification ke CI/CD

Manfaat terbesar muncul ketika contract test bukan dijalankan secara manual.

Masukkan verification ke pipeline.

Ketika consumer mengubah expectation, contract baru dipublikasikan. Provider kemudian memverifikasi apakah implementasinya kompatibel sebelum versi baru dirilis.

Panduan CI/CD Pact bahkan berfokus pada kemampuan service untuk melakukan independent deployment dengan keyakinan bahwa versi yang akan dirilis kompatibel dengan aplikasi lain tanpa harus menjalankan full end-to-end suite terlebih dahulu.

Dengan sistem tersebut, deployment decision bisa dibuat berdasarkan compatibility data.

Tim provider tidak perlu menanyakan satu per satu:

“Apakah service kalian aman kalau API kami berubah?”

Pipeline yang menjawabnya.

Namun proses ini tetap membutuhkan disiplin. Contract yang sudah tidak digunakan perlu dibersihkan, ownership harus jelas, dan versi consumer-provider harus dikelola dengan rapi.

Sedikit investasi pada infrastrktur contract testing dapat mengurangi koordinasi manual ketika jumlah service semakin besar.

Menggunakan Contract Testing membantu microservices berkembang tanpa membuat perubahan API menjadi sumber kecemasan.

Consumer dapat menyatakan kebutuhan nyata, sementara provider memverifikasi kompatibilitas sebelum deployment.

Kombinasikan contract test dengan unit, integration, dan sedikit E2E test agar feedback tetap cepat. Mulailah dari satu integrasi kritis antarlayanan, buat contract-nya, lalu masukkan verification ke pipeline CI/CD.