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?”). Artikel 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 #
flowchart TD
SI[System Integration] --> P2P["Point-to-Point\nKoneksi langsung"]
SI --> API["API-Based\nKontrak REST/gRPC/GraphQL"]
SI --> ED["Event-Driven\nMessage broker"]
SI --> FB["File-Based\nBatch CSV/XML/Parquet"]
SI --> DB["Database-Level\n❌ Anti-pattern"]1. Point-to-Point Integration #
Setiap sistem terhubung langsung ke sistem lain tanpa mediator.
flowchart LR
A[Sistem A] --> B[Sistem B]
A --> C[Sistem C]
D[Sistem D] --> BDi awal terasa simple, tapi seiring bertambahnya sistem, jumlah koneksi tumbuh secara kuadratik: N sistem menghasilkan hingga N(N-1)/2 koneksi.
| Jumlah Sistem | Maksimum Koneksi |
|---|---|
| 5 | 10 |
| 10 | 45 |
| 20 | 190 |
| 50 | 1.225 |
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.
flowchart LR
M[Mobile App] -- "REST" --> B[Backend API]
B -- "gRPC" --> I[Internal Service]
B -- "REST" --> P[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.
flowchart TD
OS[Order Service] -- "publish" --> K["(Kafka: order-events)"]
K --> PS["Payment Service\nsubscribe"]
K --> IS["Inventory Service\nsubscribe"]
K --> AS["Analytics Service\nsubscribe"]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.
flowchart LR
LE[Legacy ERP] -- "export batch\nfile.csv" --> SF[("(Shared Storage\nS3/SFTP)")]
SF -- "scheduled\nimport job" --> NS[New System]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)
// ...
}
flowchart LR
subgraph Bad["❌ Database-Level Integration"]
PS1[Payment Service] -->|"direct SQL query"| ODB[("(Order DB\nschema internal)")]
OS1[Order Service] --> ODB
end
subgraph Good["✅ API/Event sebagai Antarmuka"]
PS2[Payment Service] -->|"GET /orders/:id"| OAPI[Order Service API]
OAPI --> ODB2[("(Order DB\nencapsulated)")]
endIni 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.
sequenceDiagram
participant C as Client
participant V as Payment Validator
participant G as Payment Gateway
C->>V: POST /validate
V-->>C: {valid: true}
C->>G: POST /charge
G-->>C: {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.
sequenceDiagram
participant O as Order Service
participant Q as Queue: send-confirmation-email
participant E as Email Service
O->>Q: publish
O-->>O: return 202 (langsung selesai)
Q->>E: consume (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:
flowchart TD
OC[Order Orchestrator] -->|"1. Validate inventory"| INV[Inventory Service]
OC -->|"2. Process payment"| PAY[Payment Service]
OC -->|"3. Create shipment"| SHIP[Shipping Service]
OC -->|"4. Send notification"| NOTIF[Notification Service]Choreography — setiap service bereaksi terhadap event secara mandiri:
flowchart TD
OS[Order Service] -->|publish| EV[OrderCreated]
EV --> PS["Payment Service\nproses, publish PaymentDone"]
EV --> IS["Inventory Service\nkurangi stok, publish StockReduced"]
EV --> SS["Shipping Service\nbuat label setelah PaymentDone"]| Aspek | Orchestration | Choreography |
|---|---|---|
| Kontrol | Terpusat di satu coordinator | Terdistribusi, setiap service mandiri |
| Visibilitas flow | Jelas, satu titik monitor | Sulit dilacak, butuh distributed tracing |
| Single point of failure | Ya — coordinator | Tidak |
| Coupling | Service coupled ke coordinator | Service hanya coupled ke event schema |
| Cocok untuk | Workflow kompleks dengan banyak langkah bersyarat | Sistem yang butuh scaling dan independensi tinggi |
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.
sequenceDiagram
participant PG as Payment Gateway
participant W as Webhook Handler
PG->>W: POST /webhook\nX-Signature: sha256=...\nX-Timestamp: ...
W->>W: 1. Baca raw body
W->>W: 2. Hitung HMAC dari raw body
W->>W: 3. Bandingkan dengan X-Signature\n(constant-time)
W->>W: 4. Cek timestamp freshness\n(< 5 menit)
alt Valid
W-->>PG: 200 OK (segera)
W->>W: Proses async di background
else Invalid
W-->>PG: 401 Unauthorized
end// 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))
}
Selalu gunakanhmac.Equal(constant-time comparison), bukan==ataubytes.Equalbiasa, untuk membandingkan signature. Perbandingan biasa bisa membocorkan informasi melalui timing attack — penyerang bisa menebak signature byte demi byte dengan mengukur waktu respons.
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:
| Strategi | Contoh | Kelebihan | Kekurangan |
|---|---|---|---|
| URL versioning | /v1/orders, /v2/orders | Paling eksplisit, mudah di-route, consumer bisa migrate bertahap | URL berubah antar versi |
| 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.
sequenceDiagram
participant U as User
participant OS as Order Service
participant K as Kafka
participant PS as Payment Service
participant IS as Inventory Service
participant PG as Payment Gateway
participant NS as Notification Service
U->>OS: POST /checkout
OS->>OS: 1. Create order (PENDING)
OS->>K: 2. Publish OrderCreated
OS-->>U: 3. {order_id, status: "pending"}
K->>PS: consume OrderCreated
K->>IS: consume OrderCreated
IS->>IS: reserve stock
PS->>PG: 4. Call payment gateway\n(with idempotency key)
PG->>PS: webhook callback
PS->>PS: 5. Verify HMAC signature
PS->>PS: 6. Check idempotency
PS->>PS: 7. Update order to PAID
PS->>K: 8. Publish PaymentCompleted
K->>NS: consume PaymentCompleted
NS->>NS: 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)
})
}
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
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
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.