Siklus dependensi API internal (circular dependency) terjadi ketika Layanan A memanggil Layanan B, dan Layanan B secara langsung atau transitif memanggil kembali Layanan A untuk menyelesaikan sebuah transaksi. Pola ini menyebabkan kopling ketat, distributed deadlocks, kegagalan cascading, dan deployment pipeline yang macet karena kedua modul saling menunggu status siap.
Masalah ini serupa dengan pelanggaran hierarki pada struktur paket kode atau pembagian tim kerja: komponen tingkat rendah tidak boleh bergantung pada komponen tingkat tinggi. Mengatasi circular dependency membutuhkan boundary enforcer yang ketat di level gateway, perancangan kontrak API yang terisolasi, serta pemisahan alur otentikasi antarlayanan.
Akar Masalah: Analogi Hierarki Paket dan Kepemilikan Domain
Dalam arsitektur monolit yang bersih, prinsip Acyclic Dependencies Principle (ADP) melarang adanya siklus dalam grafik dependensi paket. Ketika beralih ke arsitektur mikro atau multi-service, prinsip ini sering kali terlanggar karena batasan jaringan menyamarkan dependensi data.
Contoh umum: OrderService memanggil BillingService untuk memproses invoice. Namun, BillingService memanggil endpoint OrderService.getDetails() untuk memvalidasi item invoice. Struktur ini membentuk siklus langsung: Order -> Billing -> Order. Jika kedua layanan membutuhkan sinkronisasi data real-time via synchronous RPC/HTTP, latensi berlipat ganda dan potensi resource exhaustion meningkat drastis.
Solusi Arsitektural: Dependency Inversion dan Event-Driven Boundary
Penyelesaian struktural dilakukan dengan dua pendekatan:
- Dependency Inversion:
OrderServicememasukkan seluruh snapshot item yang dibutuhkan saat memanggilBillingService, sehinggaBillingServicetidak perlu meminta data kembali ke pemanggil. - Domain Orchestrator: Memisahkan logika komposisi ke level orkestrator atau menggunakan asynchronous event (pub/sub).
OrderServicemenerbitkan eventOrderPlaced, danBillingServicemengonsumsi event tersebut secara mandiri.
Deteksi Statis Dependensi Menggunakan Linter Kontrak OpenAPI dan gRPC
Mencegah siklus dependensi paling efektif dilakukan pada fase integrasi (CI/CD) sebelum kode didistribusikan ke cluster target. Dependensi antar-domain dapat dipetakan langsung dari definisi kontrak OpenAPI atau gRPC proto.
Dengan mengekstraksi skema RPC/endpoint dari manifest kontrak, sistem CI dapat membangun Directed Acyclic Graph (DAG) interaksi layanan dan mendeteksi siklus menggunakan algoritma penelusuran graf standar.
# Script CI sederhana untuk validasi siklus dependensi graf (Python stdlib)
import sys
# Graph representasi dependensi layanan berdasarkan kontrak API
SERVICE_DEPENDENCIES = {
"order-service": ["billing-service", "inventory-service"],
"billing-service": ["payment-gateway-service"],
"inventory-service": ["warehouse-service"],
"warehouse-service": [] # Benar: daun dari grafik
# Jika ditambahkan "order-service" di sini, sistem harus memicu error
}
def detect_cycle(graph):
visited = set()
rec_stack = set()
def visit(node, path):
visited.add(node)
rec_stack.add(node)
for neighbor in graph.get(node, []):
if neighbor not in visited:
if visit(neighbor, path + [neighbor]):
return True
elif neighbor in rec_stack:
print(f"ERROR: Siklus terdeteksi: {' -> '.join(path + [neighbor])}")
return True
rec_stack.remove(node)
return False
for service in graph:
if service not in visited:
if visit(service, [service]):
return True
return False
if detect_cycle(SERVICE_DEPENDENCIES):
sys.exit(1)
print("Validasi kontrak lolos: Graf dependensi bersih dari siklus.")
Pada skala besar, gunakan rule kustom pada tool linter kontrak seperti Spectral untuk OpenAPI atau Buf untuk gRPC guna menandai impor paket lintas modul internal yang melanggar batasan layer (boundary violation).
Backward Compatibility dan Kontrak Data Mandiri
Kebutuhan memanggil balik layanan hulu biasanya muncul karena modifikasi skema parsial yang memecah kompatibilitas. Ketika downstream service membutuhkan atribut data tambahan namun payload request tidak memuatnya, developer cenderung membuat endpoint GET baru pada upstream service.
Pendekatan yang benar adalah menerapkan prinsip Tolerant Reader dan payload mandiri (self-contained payloads):
- Self-Contained RPC Payload: Kontrak permintaan memuat semua data identitas, status, dan metadata operasi yang dibutuhkan target service untuk menuntaskan fungsinya.
- Additive Changes Only: Field baru pada protokol gRPC atau JSON Schema ditambahkan sebagai field opsional dengan nomor urut/key unik tanpa menghapus field lama.
- Deprecation Policy: Hindari pembaruan serentak (lockstep deployment) dengan mempertahankan dukungan field versi lama minimal 1 siklus rilis penuh.
Isolasi Auth Token Exchange untuk Mencegah Deadlock Otentikasi
Penyebab siklus tersembunyi yang sering terjadi adalah runtime deadlock pada layer otentikasi/otorisasi. Pola anti-pattern terjadi ketika downstream service memverifikasi access token pengguna dengan menghubungi auth server internal, sementara auth server bergantung pada downstream service lain untuk validasi status akun.
Deadlock Alur Auth:
[Client] -> API Gateway -> [OrderService]
| (Kirim Bearer Token)
v
[ShippingService]
| (Validasi Token ke Auth Domain)
v
[AuthService]
| (Cek Status Akun Pengguna)
v
[UserService] -> [AuthService] (Deadlock)
Solusi: Asymmetric Cryptographic Verification
Hentikan panggilan balik ke auth service secara sinkron. Terapkan verifikasi token mandiri pada setiap batasan domain menggunakan Public Key Cryptography (misalnya RSA atau ECDSA via JSON Web Key Sets / JWKS):
- Setiap internal service mengunduh dan menyimpan cache Public Key dari identity provider saat boot.
- Layanan hilir memverifikasi integritas dan masa berlaku JWT secara lokal tanpa melakukan outbound network call ke AuthService.
- Jika otorisasi antarlayanan (M2M) dibutuhkan, gunakan protokol OAuth 2.0 Client Credentials Grant atau mTLS (mutual TLS) berbasis SPIFFE/SPIRE di mana identitas layanan tertanam langsung pada transport layer TLS.
Catatan Praktis: Jangan meneruskan kredensial mentah pengguna (user access token) melintasi lebih dari satu lompatan (hop) jaringan internal jika downstream service hanya perlu mengetahui identitas tenant atau ID entitas. Lakukan token exchange di perimeter boundary atau lekatkan context identity yang telah divalidasi ke internal header.
Strategi Implementasi Boundary Enforcer
Terapkan tiga langkah bertahap untuk menertibkan arsitektur integrasi:
- Gunakan Service Mesh atau API Gateway Egress Rules: Batasi akses jaringan antar-kontainer menggunakan Kubernetes NetworkPolicy atau filter routing Envoy. Jika Service B tidak memiliki alasan struktural untuk memanggil Service A, blokir rutenya di level infrastruktur.
- Wajibkan Data Contracts Repository: Satukan proto files atau OpenAPI spec dalam repository terpusat. Terapkan validasi DAG pada pull request untuk mencegah dependensi siklis sebelum kode di-merge.
- Pisahkan Read dan Write Paths (CQRS Ringan): Jika Service A butuh data agregat dari Service B dan C, alihkan dependensi ke read-only datastore terdistribusi atau downstream projection ketimbang memanggil langsung transactional endpoints.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!