Fase 3 kita tutup dengan CI gate — setiap push ke Order Service otomatis dites di GitHub Actions sebelum boleh masuk ke branch utama. Itu artinya Order Service sekarang punya fondasi yang cukup kokoh: routing beres, persistence beres, dan rangkaian test yang hijau dari unit test sampai integration test dengan Testcontainers. Fondasi itu penting, karena mulai bab ini kita akan melakukan sesuatu yang jauh lebih berisiko kalau fondasinya rapuh: memecah satu service jadi dua.
Sampai Sekarang, Kita Cuma Punya Satu Service
Ini poin yang gampang terlewat kalau langsung loncat ke "microservice". Sejak Fase 1 sampai Fase 3, semua yang kita bangun — routing, DTO, validasi, JPA, Flyway, testing — terjadi di dalam satu aplikasi Spring Boot: Order Service. Semua fungsi dipanggil langsung sebagai pemanggilan fungsi biasa di dalam satu proses, satu memory space, satu deployment.
Itu bukan kekurangan. Untuk kebutuhan sampai Fase 3, satu service yang terstruktur rapi sudah lebih dari cukup — dan justru itu pelajaran pertama yang harus digarisbawahi sebelum masuk lebih jauh: microservice bukan tujuan, dan bukan upgrade otomatis dari monolith yang sudah bagus.
Mulai bab ini, kita akan menambahkan Inventory Service sebagai service kedua yang independen — database sendiri (atau, dalam kasus sederhana yang kita pakai di sini, penyimpanan in-memory sendiri), proses deploy sendiri, port sendiri (8081, sementara Order Service tetap di 8082) — dan menghubungkan keduanya lewat panggilan network, bukan pemanggilan fungsi langsung seperti yang selama ini kita lakukan di dalam satu monolith. Perubahan ini terlihat kecil di atas kertas, tapi konsekuensinya besar: pemanggilan fungsi yang tadinya pasti berhasil (kecuali ada bug), sekarang bisa gagal karena jaringan putus, service lain lambat, atau service lain sedang restart. Bab-bab berikutnya di fase ini (26-29) semuanya berputar di sekitar konsekuensi itu.
Tiga Prinsip Inti Microservice
1. Single responsibility per service. Setiap service punya satu area tanggung jawab yang jelas, dan idealnya bisa dijelaskan dalam satu kalimat tanpa kata "dan". Order Service mengurus siklus hidup order. Inventory Service — yang akan kita bangun mulai bab ini — mengurus satu hal saja: berapa stok yang tersedia untuk satu item. Bukan kebetulan namanya cocok dengan tanggung jawabnya; kalau kamu kesulitan memberi nama service dengan satu kata benda yang jelas, itu tanda tanggung jawabnya belum cukup fokus.
2. Database-per-service. Ini aturan yang sering dilanggar duluan ketika tim buru-buru. Aturannya sederhana: tidak ada service yang boleh menjangkau langsung ke database milik service lain — baik lewat koneksi langsung, baik lewat query lintas skema, apalagi lewat foreign key lintas database. Kalau Order Service butuh data stok, dia harus minta lewat API Inventory Service, bukan SELECT langsung ke tabel stok. Kenapa ini penting? Karena begitu ada dua service yang saling mengintip database masing-masing, mereka berhenti menjadi independen — kamu tidak bisa lagi mengubah skema tabel stok tanpa mengecek dulu apakah ada service lain yang diam-diam bergantung padanya. Batas API jadi kabur, dan kamu balik lagi ke masalah monolith tapi dengan kerumitan network di atasnya.
Di kasus kita, Inventory Service bahkan belum pakai database sungguhan — datanya di mutableMapOf in-memory (akan kita lihat detailnya di bab 28-29). Tapi prinsipnya tetap sama persis: Order Service tidak tahu dan tidak peduli bagaimana Inventory Service menyimpan datanya. Yang Order Service tahu cuma satu hal — ada endpoint HTTP yang bisa ditanya. Itu bentuk paling murni dari encapsulation di level service.
3. Independent deployability. Kalau Inventory Service perlu di-deploy ulang — misalnya karena ada bug fix kecil — Order Service tidak perlu ikut di-deploy, tidak perlu ikut restart, dan idenya, tidak perlu tahu bahwa Inventory Service baru saja di-deploy sama sekali. Sebaliknya juga berlaku. Ini yang membedakan microservice dari sekadar "kode yang dipisah folder" — kalau dua "service" masih harus di-deploy bersamaan supaya tidak rusak, itu masih monolith, cuma dibungkus dua repo.
Trade-off yang Harus Dikatakan Jujur
Ada godaan untuk menjual microservice sebagai upgrade tanpa ongkos. Itu tidak jujur, dan kalau kamu mempercayainya, kamu akan kaget di bab-bab berikutnya. Yang sebenarnya terjadi adalah pertukaran, bukan peningkatan murni:
| Yang didapat | Yang dikorbankan |
|---|---|
| Tim bisa kerja paralel tanpa saling injak kode | Kompleksitas operasional naik — sekarang ada 2+ proses, 2+ port, 2+ deployment untuk dipantau |
| Satu service bisa di-scale sendiri sesuai bebannya | Panggilan fungsi yang tadinya pasti sukses sekarang bisa gagal karena jaringan (lihat bab 26 dan 29) |
| Kegagalan satu bagian tidak otomatis menjatuhkan semua (kalau didesain dengan benar) | Debugging jadi lebih susah — satu request bisa melintasi beberapa service, butuh trace yang lebih rumit (topik Fase 6, belum kita bahas di sini) |
| Setiap service bisa pakai stack/versi yang paling cocok untuknya | Testing jadi lebih rumit — butuh mock atau test double untuk service lain (kita sudah menyentuh ini sejak Fase 3, dan akan dipakai lagi di bab 29) |
Order Service kita di Fase 1-3 sudah cukup solid sebagai monolith kecil. Memecahnya jadi dua service di bab ini bukan karena itu wajib, tapi karena ini seri belajar dan kita perlu contoh nyata untuk membahas sync/async communication, contract-first design, dan API Gateway di bab-bab berikutnya — topik yang cuma masuk akal dibahas kalau ada lebih dari satu service.
Aturan Praktis: Kapan Benar-Benar Pindah ke Microservice
Di dunia nyata, aturan praktis yang paling aman adalah: mulai dari monolith yang terstruktur rapi, dan pecah jadi microservice hanya kalau ada kebutuhan konkret — biasanya salah satu dari dua ini:
- Kebutuhan scaling tim. Beberapa tim ingin deploy fitur masing-masing tanpa antre giliran atau saling menabrak kode di satu repo besar.
- Kebutuhan scaling independen. Satu bagian sistem menerima beban jauh lebih besar dari bagian lain, dan kamu ingin scale bagian itu saja tanpa ikut menggandakan seluruh aplikasi.
Kalau tidak ada salah satu dari dua alasan itu, memecah jadi microservice biasanya cuma menambah kompleksitas tanpa manfaat yang sepadan — kamu membayar ongkos di tabel di atas tanpa benar-benar memanennya. Jangan pecah service "karena arsitekturnya keren" atau "karena perusahaan besar melakukannya" — pecah karena ada masalah nyata yang microservice selesaikan lebih baik daripada monolith.
Dengan tiga prinsip dan trade-off ini di kepala, bab berikutnya masuk ke pertanyaan konkret pertama begitu kita punya dua service: kalau Order Service butuh bicara dengan Inventory Service, apakah dia harus menunggu jawabannya saat itu juga, atau bisa jalan terus dan biarkan Inventory Service menyusul belakangan?
Bagian dari Series: Kotlin Backend Microservice - Belajar Fundamental sampai Microservice Nyata
Belajar Kotlin + Spring Boot dari fundamental bahasa (null safety, OOP, coroutines) sampai membangun sistem microservice sungguhan - REST API, Postgre...
Lihat Series Lengkap