System Integration #
Hampir tidak ada sistem modern yang berdiri sendiri. Backend service memanggil payment gateway, notification service mendengarkan event dari order service, mobile app mengonsumsi REST API, scheduler menarik data dari legacy ERP. Setiap titik hubung ini adalah integrasi — dan setiap integrasi yang tidak dirancang dengan benar adalah titik lemah yang bisa menjadi sumber data inconsistency, security breach, atau cascading failure. Tantangan integrasi bukan hanya teknis (“API bisa dipanggil”) melainkan desain (“bagaimana kalau payload berubah?”, “bagaimana kalau downstream down?”, “bagaimana kalau request yang sama datang dua kali?”). Panduan ini membahas system integration dari lima jenis utamanya, tiga pola komunikasi dengan trade-off masing-masing, best practice krusial mulai dari autentikasi hingga observability, implementasi konkret webhook dan HMAC verification, hingga studi kasus end-to-end lengkap.
Lima Jenis System Integration #
1. Point-to-Point Integration #
Setiap sistem terhubung langsung ke sistem lain tanpa mediator.
Sistem A ──────→ Sistem B
│ │
└──────→ Sistem C
│
Sistem D ──────→ Sistem B
Di awal terasa simple, tapi seiring bertambahnya sistem, jumlah koneksi tumbuh secara kuadratik: N sistem menghasilkan hingga N(N-1)/2 koneksi. Dengan 10 sistem: 45 koneksi. Dengan 20 sistem: 190 koneksi. Setiap koneksi adalah dependency yang harus dikelola, dimonitor, dan diuji.
Kapan masih acceptable: prototipe, integrasi sementara antara dua sistem kecil, atau ketika kompleksitas mediator tidak sebanding.
2. API-Based Integration #
Sistem berkomunikasi melalui kontrak API yang terdefinisi — REST, gRPC, atau GraphQL.
Mobile App ──── REST ────→ Backend API ──── gRPC ────→ Internal Service
│
└──── REST ────→ Payment Gateway
Ini adalah pola yang paling umum dan paling fleksibel. Kunci yang sering dilewatkan: API bukan sekadar endpoint, ia adalah kontrak. Schema request/response yang berubah tanpa versioning adalah sumber breaking change yang merusak semua consumer sekaligus.
3. Event-Driven Integration #
Sistem berkomunikasi melalui event asynchronous melalui message broker — Kafka, RabbitMQ, AWS SNS/SQS, atau Google Pub/Sub.
Order Service ──publish──→ [Kafka: order-events]
│
┌──────────────┼──────────────┐
↓ ↓ ↓
Payment Svc Inventory Svc Analytics Svc
(subscribe) (subscribe) (subscribe)
Keunggulan: loose coupling yang sesungguhnya — Order Service tidak tahu siapa yang mendengarkan eventnya. Menambahkan consumer baru tidak memerlukan perubahan pada producer. Kelemahannya: debugging lebih kompleks, dan sistem beroperasi dengan eventual consistency.
4. File-Based Integration #
Pertukaran data melalui file (CSV, XML, JSON, Parquet) yang ditempatkan di lokasi yang disepakati.
Paling umum di integrasi dengan sistem legacy (ERP, mainframe) yang tidak mendukung API modern, atau untuk batch job volume besar. Risikonya: delay tinggi (data baru tersedia setelah batch selesai), error handling sulit (file rusak di tengah proses), dan tidak ada konfirmasi real-time.
5. Database-Level Integration — Anti-Pattern #
Satu sistem mengakses database sistem lain secara langsung.
// ANTI-PATTERN: Payment Service langsung query DB milik Order Service
func (p *PaymentService) getOrderTotal(orderID string) float64 {
row := orderDB.QueryRow( // ← akses database milik sistem lain!
"SELECT total FROM orders WHERE id = ?", orderID)
// ...
}
Ini melanggar encapsulation sepenuhnya: schema database menjadi public API yang tidak bisa diubah tanpa merusak konsumen. Payment Service sekarang bergantung pada internal implementation detail Order Service. Tidak ada cara untuk evolve schema database tanpa koordinasi dengan semua pihak yang mengaksesnya. Selalu gunakan API atau event sebagai antarmuka.
Tiga Pola Komunikasi Integrasi #
Synchronous Request-Response #
Client menunggu response sebelum melanjutkan. Cocok ketika hasil langsung dibutuhkan.
Client ──── POST /validate ──→ Payment Validator
←─── {valid: true} ────
Client ──── POST /charge ────→ Payment Gateway
←─── {txn_id: "abc"} ──
Kapan pakai sync: validasi data yang menentukan apakah flow bisa dilanjutkan, query yang hasilnya langsung ditampilkan ke user, operasi dengan SLA latency ketat.
Risikonya: cascading failure — jika downstream lambat atau down, caller ikut terhambat atau gagal. Harus selalu dilengkapi timeout dan circuit breaker.
Asynchronous Messaging #
Producer mengirim message ke broker dan langsung selesai. Consumer memproses di waktu yang berbeda.
Order Service ──publish──→ [Queue: send-confirmation-email]
return 202 Consumer ──→ Email Service (proses background)
Kapan pakai async: operasi yang tidak perlu hasilnya langsung (kirim email, generate report, sync ke CRM), proses yang membutuhkan waktu lama, operasi yang boleh eventual consistency.
Orchestration vs Choreography #
Dua pendekatan berbeda untuk mengkoordinasikan banyak service dalam sebuah workflow.
Orchestration — ada central coordinator yang mengarahkan workflow:
Order Orchestrator
│
├── 1. Validate inventory ──→ Inventory Service
├── 2. Process payment ──────→ Payment Service
├── 3. Create shipment ──────→ Shipping Service
└── 4. Send notification ───→ Notification Service
Keunggulan: flow jelas, mudah di-debug, satu titik untuk monitor status
Kelemahan: single point of failure, coupling ke orchestrator
Choreography — setiap service bereaksi terhadap event secara mandiri:
Order Service ──publish──→ OrderCreated
│
┌───────────┼───────────┐
↓ ↓ ↓
Payment Svc Inventory Shipping
proses, kurangi buat label
publish stok setelah
PaymentDone publish PaymentDone
StockReduced
Keunggulan: setiap service independent, tidak ada single point of failure, mudah scale. Kelemahan: flow sulit dilacak, debugging kompleks, butuh distributed tracing yang baik.
Best Practice Krusial #
Authentication — Jangan Percaya Request dari Sistem Lain #
Setiap integrasi harus diautentikasi, termasuk internal service-to-service. “Internal network” bukan jaminan keamanan.
// Machine-to-machine auth dengan OAuth 2.0 Client Credentials
type ServiceClient struct {
tokenURL string
clientID string
clientSecret string
httpClient *http.Client
tokenCache *TokenCache
}
func (c *ServiceClient) GetAccessToken(ctx context.Context) (string, error) {
// Cek cache dulu — token valid selama 5 menit sebelum expired
if token, ok := c.tokenCache.Get(); ok {
return token, nil
}
resp, err := c.httpClient.PostForm(c.tokenURL, url.Values{
"grant_type": {"client_credentials"},
"client_id": {c.clientID},
"client_secret": {c.clientSecret},
"scope": {"internal:read internal:write"},
})
// parse token dan cache dengan TTL = expires_in - 5 menit buffer
token := parseToken(resp)
c.tokenCache.Set(token, token.ExpiresIn-5*time.Minute)
return token.AccessToken, nil
}
Pilihan autentikasi berdasarkan konteks:
Konteks Mekanisme yang Direkomendasikan
────────────────────────────── ─────────────────────────────────
Internal service (low trust) OAuth 2.0 Client Credentials + short-lived JWT
Internal service (high trust) mTLS (mutual TLS) + JWT
External webhook HMAC signature + timestamp
External third-party API API Key + TLS
Mobile/web → backend OAuth 2.0 Authorization Code + PKCE
HMAC Webhook Signature Verification #
Webhook dari payment gateway, GitHub, Stripe, dan layanan serupa biasanya mengirim HMAC signature untuk membuktikan request berasal dari mereka dan payload tidak dimodifikasi.
// Webhook handler dengan HMAC signature verification
func (h *PaymentWebhookHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
// Baca raw body SEBELUM parsing — signature dihitung dari raw bytes
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
// Verifikasi signature
if !h.verifySignature(body, r.Header.Get("X-Webhook-Signature")) {
log.Warnf("invalid webhook signature from %s", r.RemoteAddr)
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Cek timestamp untuk mencegah replay attack
timestamp := r.Header.Get("X-Webhook-Timestamp")
if !h.isTimestampFresh(timestamp, 5*time.Minute) {
log.Warnf("stale webhook timestamp: %s", timestamp)
http.Error(w, "Request expired", http.StatusUnauthorized)
return
}
// Return 200 SEGERA — proses async di background
// Webhook provider biasanya timeout dalam 5-30 detik
w.WriteHeader(http.StatusOK)
// Proses di goroutine terpisah agar tidak timeout
go h.processWebhookAsync(body)
}
func (h *PaymentWebhookHandler) verifySignature(body []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(h.webhookSecret))
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
// Gunakan hmac.Equal untuk constant-time comparison
// — mencegah timing attack
return hmac.Equal([]byte(signature), []byte(expected))
}
Data Contract dan Versioning #
Schema adalah kontrak. Perubahan yang tidak backward-compatible merusak semua consumer tanpa pemberitahuan.
// BENAR: versioned API response — tambah field, jangan hapus
type OrderResponseV1 struct {
OrderID string `json:"order_id"`
Status string `json:"status"`
Total float64 `json:"total"`
CreatedAt time.Time `json:"created_at"`
}
// V2: tambah field baru sebagai optional — consumer V1 tidak rusak
type OrderResponseV2 struct {
OrderID string `json:"order_id"`
Status string `json:"status"`
Total float64 `json:"total"`
CreatedAt time.Time `json:"created_at"`
// Field baru — omitempty agar tidak breaking untuk consumer lama
TrackingNumber *string `json:"tracking_number,omitempty"`
PromotionCode *string `json:"promotion_code,omitempty"`
}
Strategi versioning API:
URL versioning: /v1/orders, /v2/orders
→ Paling eksplisit, mudah di-route
→ Consumer bisa migrate secara bertahap
Header versioning: Accept: application/vnd.api.v2+json
→ URL bersih
→ Kurang discoverable
Query param: /orders?version=2
→ Simple
→ Tidak standar
Idempotency Key untuk Operasi Kritis #
Setiap operasi yang tidak idempotent secara alami (create, charge) harus dibuat idempotent dengan idempotency key.
// Client menyertakan idempotency key di setiap request kritis
func (c *PaymentClient) CreateCharge(ctx context.Context, req ChargeRequest) (*Charge, error) {
// Generate idempotency key berdasarkan intent — bukan random
// Key yang sama untuk request yang sama memastikan deduplication bekerja
idempotencyKey := fmt.Sprintf("charge:%s:%s:%d",
req.OrderID,
req.UserID,
req.AmountCents,
)
httpReq, _ := http.NewRequestWithContext(ctx, "POST",
c.baseURL+"/charges",
encodeJSON(req),
)
httpReq.Header.Set("Idempotency-Key", idempotencyKey)
httpReq.Header.Set("X-Correlation-ID", getCorrelationID(ctx))
// Server akan deduplicate: jika key sudah ada, return hasil lama
return c.do(httpReq)
}
Observability — Correlation ID di Seluruh Sistem #
Tanpa distributed tracing, debugging request yang melewati 5 service adalah mimpi buruk.
// Middleware yang propagasi correlation ID ke semua outbound request
type CorrelationMiddleware struct {
next http.RoundTripper
}
func (m *CorrelationMiddleware) RoundTrip(req *http.Request) (*http.Response, error) {
// Ambil dari context, atau generate jika tidak ada
correlationID := getOrGenerateCorrelationID(req.Context())
// Propagate ke downstream
req.Header.Set("X-Correlation-ID", correlationID)
req.Header.Set("X-Request-ID", uuid.New().String())
return m.next.RoundTrip(req)
}
// Semua log harus menyertakan correlation ID
func logRequest(ctx context.Context, msg string, fields ...interface{}) {
cid := ctx.Value("correlation_id")
log.WithField("correlation_id", cid).Info(append([]interface{}{msg}, fields...)...)
}
Studi Kasus: Order → Payment → Notification #
Berikut flow end-to-end yang menerapkan semua best practice yang sudah dibahas.
User ──POST /checkout──→ Order Service
│
│ 1. Create order (PENDING)
│ 2. Publish event OrderCreated
│ 3. Return {order_id, status: "pending"}
↓
[Kafka: order-events]
│
┌───────────────┤
↓ ↓
Payment Service Inventory Service
consume event consume event
initiate charge reserve stock
│
│ 4. Call payment gateway (with idempotency key)
↓
Payment Gateway ──── webhook ──→ Payment Service
│
5. Verify HMAC signature
6. Check idempotency
7. Update order to PAID
8. Publish PaymentCompleted
│
↓
[Kafka: payment-events]
│
↓
Notification Service
consume PaymentCompleted
send email + push notification
// Payment Service — handler untuk webhook dari gateway
func (s *PaymentService) HandleGatewayWebhook(ctx context.Context,
payload WebhookPayload, signature string) error {
// Verifikasi signature
if !verifyHMAC(payload, signature, s.webhookSecret) {
return ErrInvalidSignature
}
// Idempotency check — webhook bisa dikirim ulang
if processed, _ := s.processedWebhooks.Exists(ctx, payload.WebhookID); processed {
log.Infof("webhook %s already processed, skipping", payload.WebhookID)
return nil
}
// Proses dalam satu transaction
return s.db.Transaction(func(tx *gorm.DB) error {
// Update order status
if err := s.orderRepo.UpdateStatus(ctx, tx,
payload.OrderID, "PAID"); err != nil {
return err
}
// Publish event
if err := s.eventBus.Publish(ctx, PaymentCompletedEvent{
OrderID: payload.OrderID,
TransactionID: payload.TransactionID,
Amount: payload.Amount,
PaidAt: time.Now(),
CorrelationID: getCorrelationID(ctx),
}); err != nil {
return err
}
// Tandai webhook sebagai processed
return s.processedWebhooks.Set(ctx, tx, payload.WebhookID)
})
}
Checklist Integrasi Production-Ready #
KEAMANAN:
□ Semua integrasi diautentikasi (tidak ada "trusted by default")
□ Token/secret disimpan di secret manager (bukan hardcoded)
□ Short-lived token dengan rotation
□ Webhook signature diverifikasi dengan HMAC
□ Timestamp freshness check untuk mencegah replay attack
□ TLS untuk semua komunikasi
DATA CONTRACT:
□ API schema terdokumentasi (OpenAPI/Protobuf)
□ Versioning strategy didefinisikan
□ Perubahan backward-compatible (tambah field, jangan hapus)
□ Breaking change ditangani dengan major version baru
RELIABILITY:
□ Timeout dikonfigurasi untuk semua outbound call
□ Retry dengan exponential backoff + jitter
□ Circuit breaker untuk downstream yang sering flaky
□ Idempotency key untuk operasi yang tidak idempotent secara alami
□ DLQ untuk event yang gagal diproses
OBSERVABILITY:
□ Correlation ID di-generate di entry point dan dipropagasi ke semua downstream
□ Setiap request/response di-log dengan correlation ID
□ Metrics: error rate, latency, throughput per integration point
□ Alert untuk error rate yang melebihi threshold
Anti-Pattern yang Harus Dihindari #
// ✗ Trust internal request tanpa autentikasi
func handleInternalRequest(r *http.Request) {
// "Ini dari internal, pasti aman"
processRequest(r) // tidak ada auth check
}
// ✓ Verifikasi JWT atau mTLS bahkan untuk internal call
// ✗ Tidak ada timeout pada outbound call
resp, err := http.Get("https://external-api.com/data")
// Jika external API hang, goroutine ini hang selamanya
// ✓ Selalu set timeout
client := &http.Client{Timeout: 10 * time.Second}
// ✗ Menelan error dari downstream tanpa logging
result, err := externalService.Call(req)
if err != nil {
return nil // error hilang tanpa jejak
}
// ✓ Log dengan context, propagate atau wrap dengan informasi yang cukup
// ✗ Hardcoded credentials
const apiKey = "sk_live_abc123" // ← di Git, terekspos ke semua developer
// ✓ Baca dari secret manager atau environment variable yang aman
// ✗ Webhook langsung diproses synchronously tanpa queue
func handleWebhook(w http.ResponseWriter, r *http.Request) {
processHeavyOperation() // bisa timeout sebelum return 200
w.WriteHeader(200)
}
// ✓ Return 200 segera, proses async di background
Ringkasan #
- System integration menghubungkan sistem agar bisa bertukar data dan berkolaborasi — bukan sekadar “API saling panggil”, melainkan menyangkut kontrak, keamanan, dan reliability jangka panjang.
- Lima jenis: point-to-point (simple tapi tidak scalable), API-based (standar dan fleksibel), event-driven (loose coupling tapi eventual consistency), file-based (untuk legacy/batch), dan database-level (anti-pattern — hindari).
- Orchestration memberikan visibilitas flow yang jelas dengan central coordinator; choreography memberikan independence lebih tapi flow lebih sulit dilacak — pilih berdasarkan kebutuhan.
- Autentikasi wajib bahkan untuk internal service — gunakan OAuth 2.0 Client Credentials atau mTLS, bukan “trusted by default”.
- HMAC signature verification untuk webhook — termasuk constant-time comparison dan freshness check untuk mencegah timing attack dan replay attack.
- Idempotency key wajib untuk operasi yang tidak secara alami idempotent — payment, order creation, aksi yang punya efek finansial.
- Return 200 segera dari webhook endpoint, proses async di background — webhook provider biasanya timeout dalam 5–30 detik.
- API adalah kontrak — perubahan schema harus backward-compatible; breaking change butuh major version baru.
- Correlation ID harus di-generate di entry point dan dipropagasi ke semua downstream — tanpa ini, debugging request yang melewati banyak service sangat sulit.