Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
05 Aug 2026 · 21 mnt baca ·Artikel 16 / 208
Go

SDD untuk Microservice Golang: API Contract antar Service dengan Claude Code

Cara menerapkan Specification-Driven Development di microservice Golang. API contract antar service, event schema, consumer-driven contract testing dengan Claude Code.

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

SDD untuk Microservice Go: API Contract antar Service

SDD di microservice Golang menjadi krusial justru karena satu fitur jarang berhenti di satu service. Di monolith, satu spec mengatur satu endpoint. Di microservice, setiap fitur bisa menyentuh tiga, empat, atau lebih service — masing-masing dengan contract yang perlu disepakati lebih dulu.

Inilah di mana SDD punya nilai paling tinggi. API contract antar service yang didefinisikan dengan baik adalah perbedaan antara tim yang bisa deploy independen dan tim yang selalu harus “koordinasi dulu sebelum deploy.”

Di artikel ini, kita bahas bagaimana SDD bekerja di lingkungan microservice, dengan contoh konkret dari Santekno Shop: dari tiga jenis contract, consumer-driven contract testing, event schema versioning, sampai deteksi drift lintas service.


16.1 Tantangan Spesifik Microservice

Di monolith, communication antar komponen adalah function call — type-safe dan compile-time checked. Di microservice, communication adalah network call — runtime failure, version mismatch, dan partial success jadi hal biasa. Diagram berikut menyandingkan dua dunia itu agar terlihat di mana letak risikonya.

text
1Monolith:
2OrderService.CreateOrder(input) → compile error jika signature salah
3
4Microservice:
5POST http://product-service/internal/check-stock → runtime 500 jika payload salah

Perbedaan intinya jelas: di monolith compiler menangkap ketidaksesuaian, di microservice tidak ada compiler yang menjaga kesepakatan antara apa yang Order Service kirim dan apa yang Product Service harapkan. Inilah mengapa explicit contract specification wajib ada sejak awal.


16.2 Tiga Jenis Contract di Microservice Environment

Sebelum masuk ke tooling, kita perlu sepakat dulu bahwa “contract” di microservice bukan satu hal tunggal. Ada tiga jenis yang masing-masing punya spec sendiri.

Contract 1: Synchronous HTTP API Contract

Contract pertama mengatur service yang berkomunikasi secara synchronous (request-response). Spec berikut mendefinisikan endpoint cek stok yang dipanggil Order Service sebelum membuat order, lengkap dengan semua response code-nya.

markdown
 1# specs/contracts/order-product-sync.md
 2
 3## Contract: Order Service → Product Service (Synchronous)
 4
 5### Check Stock Availability
 6Endpoint: GET http://product-service/internal/v1/products/{id}/stock
 7Called by: Order Service sebelum create order (AC3 di create-order.md)
 8Auth: mTLS + X-Service-Token header
 9
10Request:
11- Path: product_id (UUID)
12- Query: quantity (int, min=1)
13
14Response 200:
15{
16  "available": true,
17  "current_stock": 100,
18  "reserved_stock": 5
19}
20
21Response 404:
22{
23  "error_code": "PRODUCT_NOT_FOUND"
24}
25
26Response 422:
27{
28  "error_code": "INSUFFICIENT_STOCK",
29  "available": 3,
30  "requested": 5
31}

Dengan setiap response code dan payload dituliskan eksplisit, Product Service tahu persis apa yang harus dikembalikan dan Order Service tahu persis apa yang harus di-handle — tidak ada ruang untuk asumsi.

Contract 2: Async Event Contract

Contract kedua untuk komunikasi via event atau message queue. Spec event berikut mendefinisikan ORDER_CREATED beserta publisher, consumer, schema, dan jaminan pengiriman yang harus dipatuhi semua pihak.

markdown
 1# specs/contracts/order-events.md
 2
 3## Event: ORDER_CREATED
 4
 5Publisher: Order Service
 6Consumer: Notification Service, Analytics Service, Inventory Service
 7
 8Topic: order-events
 9Partition key: order_id (for ordering guarantee per order)
10
11Schema v1.0:
12{
13  "event_type": "ORDER_CREATED",
14  "event_version": "1.0",
15  "event_id": "uuid",
16  "occurred_at": "ISO 8601 UTC",
17  "payload": {
18    "order_id": "uuid",
19    "user_id": "uuid",
20    "total_amount_cents": "int64",
21    "currency": "IDR",
22    "items": [{
23      "product_id": "uuid",
24      "product_name": "string",
25      "quantity": "int",
26      "price_cents": "int64"
27    }]
28  }
29}
30
31Consumer Guarantees:
32- At-least-once delivery
33- Consumers MUST be idempotent (check event_id)
34- Message ordering per partition (same order_id → same partition)

Bagian Consumer Guarantees inilah yang paling sering dilupakan namun paling penting: ia memberi tahu consumer bahwa event bisa datang lebih dari sekali, sehingga idempotency menjadi kewajiban, bukan opsi.

Contract 3: Data Contract (Alternatif Anti-Pattern Shared Database)

Contract ketiga menghindari shared database dengan mendefinisikan “query API” yang eksplisit. Spec berikut memberi Analytics Service jalur resmi untuk membaca agregat tanpa menyentuh database Order Service secara langsung.

markdown
 1# specs/contracts/order-read-api.md
 2
 3## Read API: Order Query Service
 4
 5Digunakan oleh Analytics Service untuk mendapat aggregate data
 6TANPA direct DB access ke Order Service database.
 7
 8Endpoint: GET /internal/v1/orders/analytics/daily-summary
 9Auth: X-Service-Token: analytics-service-token
10
11Response:
12{
13  "date": "2025-07-01",
14  "total_orders": 1523,
15  "total_revenue_cents": 152300000,
16  "cancelled_orders": 45,
17  "status_breakdown": {
18    "PENDING": 120,
19    "CONFIRMED": 800,
20    "SHIPPED": 400,
21    "DELIVERED": 158,
22    "CANCELLED": 45
23  }
24}

Dengan query API eksplisit seperti ini, Order Service tetap memegang kendali penuh atas skema database internalnya — perubahan tabel tidak akan diam-diam merusak Analytics Service selama kontrak read API dijaga.


16.3 Consumer-Driven Contract Testing

Di microservice SDD, pendekatan yang paling robust adalah Consumer-Driven Contract Testing (CDCT). Perbandingan singkat berikut menjelaskan kenapa arah “consumer yang menentukan” lebih aman daripada “provider yang mendikte”.

text
1Traditional approach (Provider-driven):
2Provider defines contract → Consumers adapt
3
4Consumer-Driven Contract:
5Consumer defines what it NEEDS → Provider implements those needs
6Provider tests prove it satisfies consumer contracts

Inti perbedaannya: provider hanya tahu apa yang bisa dia berikan, sedangkan consumer tahu apa yang benar-benar dia butuhkan — sehingga contract yang lahir dari consumer lebih kecil kemungkinannya menghasilkan field yang tidak terpakai. Implementasi CDCT dengan Pact di Golang terlihat seperti test berikut, di mana Notification Service mendeklarasikan ekspektasinya terhadap Order Service.

go
 1// Notification Service (Consumer) defines its contract
 2// notification-service/internal/contract/order_contract_test.go
 3
 4func TestOrderServiceContract(t *testing.T) {
 5    mockProvider := dsl.NewPact(dsl.Config{
 6        Consumer: "notification-service",
 7        Provider: "order-service",
 8    })
 9
10    // Define what Notification Service expects from ORDER_CREATED event
11    mockProvider.
12        AddInteraction().
13        Given("an order has been created").
14        UponReceiving("ORDER_CREATED event").
15        WithRequest(dsl.Request{
16            Method: "GET",
17            Path:   dsl.String("/internal/v1/orders/123"),
18        }).
19        WillRespondWith(dsl.Response{
20            Status: 200,
21            Body: map[string]interface{}{
22                "order_id":   "123",
23                "user_email": dsl.Like("user@example.com"),
24                "total_amount_cents": dsl.Like(150000),
25            },
26        })
27
28    // Run the test
29    err := mockProvider.Verify(t, func() error {
30        // Call the endpoint using Notification Service's client code
31        client := order.NewClient("http://localhost:" + mockProvider.Server.Port)
32        order, err := client.GetOrder(context.Background(), "123")
33        assert.NoError(t, err)
34        assert.Equal(t, "123", order.OrderID)
35        return err
36    })
37    assert.NoError(t, err)
38}

Test ini menghasilkan sebuah pact file yang menjadi kontrak resmi: Order Service nantinya wajib memverifikasi bahwa dirinya memenuhi ekspektasi ini, sehingga breaking change langsung ketahuan di CI, bukan di production.


16.4 Spec untuk Multi-Service Feature

Ketika sebuah fitur melibatkan banyak service, spec-nya perlu memetakan semua service sekaligus alur interaksinya. Spec berikut merangkum fitur “Customer Cancels Order” dari titik masuk sampai cross-service acceptance criteria.

markdown
 1# specs/features/customer-cancels-order.md
 2
 3## Feature: Customer Cancels Order (Multi-Service)
 4
 5### Overview
 6Seorang customer membatalkan order yang masih dalam status PENDING.
 7Fitur ini melibatkan tiga service.
 8
 9## Service Interaction Flow
10
11Customer
12  ↓ DELETE /orders/{id}
13Order Service
14  ↓ UPDATE order status to CANCELLED + stock restore
15  ↓ PUBLISH ORDER_CANCELLED event
16  ↓ 204 No Content → Customer
17
18Kafka (ORDER_CANCELLED event)
19  ↓ consume
20Notification Service
21  ↓ send cancellation email → Customer
22
23Kafka (ORDER_CANCELLED event)
24  ↓ consume
25Analytics Service
26  ↓ update cancellation rate metric
27
28## Order Service Spec
29→ Lihat: specs/order/cancel-order.md v1.3
30
31## Notification Service Contract
32→ Lihat: specs/contracts/notification-order-cancelled.md
33
34## Analytics Service Contract
35→ Lihat: specs/contracts/analytics-order-events.md
36
37## Cross-Service Acceptance Criteria
38
39- XAC1: Ketika customer cancel order berhasil, email konfirmasi terkirim dalam 60 detik
40- XAC2: Analytics dashboard update dalam 5 menit setelah cancel
41- XAC3: Jika Notification Service down, Order Service masih return 204
42         (notifikasi adalah eventual consistency)
43- XAC4: Jika Analytics Service down, Order Service masih return 204

Perhatikan XAC3 dan XAC4: keduanya menegaskan bahwa kegagalan service downstream tidak boleh menggagalkan aksi utama customer — sebuah keputusan desain resiliensi yang harus tertulis di spec agar tidak diperdebatkan lagi saat implementasi.


16.5 Claude Code untuk Multi-Service Spec

Menyusun contract untuk banyak consumer sekaligus adalah tugas yang cocok didelegasikan ke Claude Code. Prompt berikut memberi Claude Code konteks lengkap tiga service dan meminta output yang terstruktur, bukan sekadar draft acak.

text
 1Saya sedang membuat fitur "Customer Cancels Order" yang melibatkan:
 2- Order Service (service ini)
 3- Notification Service (dikembangkan Tim B)
 4- Analytics Service (dikembangkan Tim C)
 5
 6Bantu saya membuat:
 7
 81. ORDER_CANCELLED event schema yang lengkap untuk dikonsumsi
 9   oleh Notification Service dan Analytics Service
10
112. Contract spec untuk setiap consumer:
12   - Apa yang Notification Service butuhkan dari event ini?
13   - Apa yang Analytics Service butuhkan dari event ini?
14
153. Backward compatibility rules:
16   - Field apa yang tidak boleh dihapus?
17   - Bagaimana versioning event schema?
18
19Context:
20- Notification Service butuh: user email, order summary, total amount
21- Analytics Service butuh: order_id, user_id, cancelled_at, reason
22- Keduanya akan berjalan di Kafka consumer group yang berbeda

Kunci prompt ini ada pada bagian Context di akhir: dengan menyebutkan field yang dibutuhkan tiap consumer secara eksplisit, Claude Code bisa langsung memvalidasi apakah event schema yang dia usulkan sudah menutup semua kebutuhan — mengurangi bolak-balik revisi.


16.6 API Gateway Contract

Untuk API yang diekspos lewat API Gateway, contract perlu memisahkan endpoint publik dari endpoint internal. Spec berikut menegaskan mana yang boleh diakses aplikasi customer dan mana yang khusus service-to-service.

markdown
 1# specs/contracts/api-gateway-order.md
 2
 3## API Gateway Contract: Order Service
 4
 5### Public Endpoints (via API Gateway)
 6Diekspos ke customer-facing apps (web, mobile)
 7Auth: JWT Bearer token dari Auth Service
 8
 9- POST /v1/orders → Order Service /orders
10- GET /v1/orders/{id} → Order Service /orders/{id}
11- DELETE /v1/orders/{id} → Order Service /orders/{id}
12- GET /v1/orders → Order Service /orders (paginated)
13
14### Internal Endpoints (NOT via Gateway)
15Service-to-service, via internal network only
16Auth: mTLS + X-Service-Token
17
18- GET /internal/v1/orders/{id} → Full order data for internal services
19- POST /internal/v1/orders/{id}/events → Webhook from Payment Service
20
21### Rate Limits (enforced at Gateway)
22- Authenticated user: 100 req/min
23- Per IP: 1000 req/min

Pemisahan Public versus Internal ini adalah batas keamanan yang konkret: endpoint /internal/ tidak pernah menyentuh Gateway, sehingga tidak mungkin terekspos ke internet publik meski secara tidak sengaja.


16.7 Event Schema Versioning

Salah satu tantangan terbesar di event-driven microservice adalah schema evolution. Spec policy berikut membedakan dengan tegas mana perubahan yang aman dan mana yang breaking, plus proses migrasinya.

markdown
 1## Event Schema Versioning Policy
 2
 3### Backward-Compatible Changes (OK to add without version bump)
 4- Tambah optional field baru
 5- Tambah nilai enum baru (consumer harus handle unknown)
 6- Relaxing validation (max_length dari 100 ke 200)
 7
 8### Breaking Changes (require new version + migration period)
 9- Hapus atau rename field
10- Mengubah tipe data
11- Tightening validation
12
13### Migration Process untuk Breaking Changes
14
151. Publish v1 dan v2 BERSAMAAN selama 30 hari:
16   Topic: order-events (v2 events berjalan parallel)
17   Consumer: subscribe ke kedua, implementasi handling untuk keduanya
18
192. Setelah 30 hari: deprecated v1
20   Warning di header: X-Schema-Deprecated: true
21
223. Setelah 60 hari: hapus v1 support

Aturan ini mengubah pertanyaan subjektif “apakah perubahan ini aman?” menjadi checklist objektif: kalau menghapus atau me-rename field, itu breaking dan wajib melewati periode migrasi 30–60 hari. Prinsip versioning ini kemudian diterapkan di kode publisher seperti berikut.

go
 1// order-service/internal/kafka/publisher.go
 2
 3type OrderEventPublisher struct {
 4    producer *kafka.Producer
 5    topic    string
 6}
 7
 8// PublishOrderCancelled publishes ORDER_CANCELLED event v1.1
 9// Schema: specs/contracts/order-events.md#ORDER_CANCELLED
10func (p *OrderEventPublisher) PublishOrderCancelled(
11    ctx context.Context,
12    orderID, userID uuid.UUID,
13    items []OrderItem,
14) error {
15    event := OrderCancelledEventV1_1{
16        EventType:    "ORDER_CANCELLED",
17        EventVersion: "1.1",
18        EventID:      uuid.New().String(),
19        OccurredAt:   time.Now().UTC().Format(time.RFC3339),
20        Payload: OrderCancelledPayload{
21            OrderID:   orderID.String(),
22            UserID:    userID.String(),
23            Items:     convertItems(items),
24            // v1.1 addition: cancelled_at (was missing in v1.0)
25            CancelledAt: time.Now().UTC().Format(time.RFC3339),
26        },
27    }
28
29    data, err := json.Marshal(event)
30    if err != nil {
31        return fmt.Errorf("marshal event: %w", err)
32    }
33
34    return p.producer.Produce(&kafka.Message{
35        TopicPartition: kafka.TopicPartition{
36            Topic:     &p.topic,
37            Partition: kafka.PartitionAny,
38        },
39        Key:   []byte(orderID.String()), // partition by order_id
40        Value: data,
41        Headers: []kafka.Header{
42            {Key: "schema-version", Value: []byte("1.1")},
43            {Key: "service-name", Value: []byte("order-service")},
44        },
45    }, nil)
46}

Dua detail kunci di kode ini: EventVersion ditanam di payload dan schema-version di header Kafka. Dengan begitu consumer bisa memutuskan cara memproses tiap event berdasarkan versinya, dan proses migrasi paralel v1/v2 di atas benar-benar bisa berjalan.


16.8 Contract Testing dalam CI/CD

Contract yang tidak diuji otomatis akan pelan-pelan menyimpang. Workflow berikut menjalankan provider verification dan cek kompatibilitas schema pada setiap push dan pull request.

yaml
 1# .github/workflows/contract-test.yml
 2name: Contract Tests
 3
 4on:
 5  push:
 6    branches: [main, develop]
 7  pull_request:
 8
 9jobs:
10  provider-contract-test:
11    name: Verify Order Service satisfies consumer contracts
12    runs-on: ubuntu-latest
13
14    services:
15      postgres:
16        image: postgres:15
17        env:
18          POSTGRES_DB: shop_test
19
20      pact-broker:
21        image: pactfoundation/pact-broker
22
23    steps:
24      - name: Run provider verification
25        run: |
26          # Download consumer contracts from Pact Broker
27          # Verify Order Service satisfies all consumer expectations
28          go test ./internal/contract/... -run TestProviderVerification
29
30      - name: Check event schema compatibility
31        run: |
32          # Verify ORDER_CANCELLED schema is compatible with consumers
33          go run scripts/check-schema-compat.go \
34            --schema specs/contracts/order-events.md \
35            --consumers notification-service,analytics-service

Dengan dua step ini di CI, kegagalan contract menjadi merah di PR sebelum kode di-merge — persis seperti unit test yang gagal, sehingga tidak ada breaking change yang lolos diam-diam ke branch utama.


16.9 Menggunakan Claude Code untuk Contract Consistency Check

Selain menulis contract, Claude Code juga andal memeriksa konsistensi antar producer dan consumer. Prompt berikut memintanya menghasilkan compatibility matrix yang bisa langsung dibaca reviewer.

text
 1Saya punya tiga service dengan event contracts:
 2
 3Order Service publishes ORDER_CANCELLED:
 4@api/events/order-cancelled.json
 5
 6Notification Service consumes ORDER_CANCELLED:
 7@internal/kafka/consumer.go
 8
 9Analytics Service consumes ORDER_CANCELLED:
10@internal/kafka/consumer.go
11
12Tolong lakukan contract compatibility check:
131. Apakah semua field yang di-consume oleh Notification Service ada di event schema?
142. Apakah semua field yang di-consume oleh Analytics Service ada di event schema?
153. Apakah ada field yang satu consumer expect tapi tidak ada di schema?
164. Apakah ada type mismatch antar producer dan consumer?
17
18Output: compatibility matrix
19| Field | Schema | Notif Consumer | Analytics Consumer | Compatible? |
20|-------|--------|---------------|-------------------|-------------|

Dengan memaksa output ke format matrix, hasil pengecekan menjadi mudah di-scan: satu baris merah di kolom Compatible? langsung menandai field yang perlu diperbaiki sebelum deploy.


16.10 Service Dependency Mapping

Di lingkungan microservice yang kompleks, memvisualisasikan dependency membantu menemukan titik rawan. Prompt berikut memberi Claude Code peta dependency Santekno Shop dan meminta analisis blast radius.

text
 1Buat service dependency map untuk Santekno Shop:
 2
 3Order Service:
 4- Consumes from: Product Service (stock check), User Service (address validation)
 5- Publishes to: Kafka (ORDER_CREATED, ORDER_CANCELLED, ORDER_SHIPPED)
 6
 7Notification Service:
 8- Consumes from: Kafka (all order events)
 9- Publishes to: (none)
10
11Product Service:
12- Consumes from: (none)
13- Publishes to: Kafka (PRODUCT_UPDATED, STOCK_DEPLETED)
14
15Analytics Service:
16- Consumes from: Kafka (all events)
17- Reads from: Order Service Read API
18
19Dari dependency map ini:
201. Identifikasi single points of failure
212. Identifikasi circular dependency (jika ada)
223. Beri rekomendasi untuk isolate blast radius jika Order Service down

Analisis semacam ini menyingkap risiko yang tidak terlihat dari kode satu service saja — misalnya bahwa Order Service adalah single point of failure karena tiga service lain bergantung padanya, sehingga isolasinya jadi prioritas arsitektur.


16.11 Spec untuk Saga Pattern

Untuk fitur yang butuh distributed transaction, Saga pattern lebih tepat daripada transaksi lintas database. Spec berikut mendefinisikan langkah-langkah saga Create Order beserta compensating transaction-nya.

markdown
 1# specs/sagas/create-order-saga.md
 2
 3## Saga: Create Order
 4
 5### Overview
 6Koordinasi antara Order Service, Inventory Service, dan Payment Service
 7untuk complete order creation tanpa distributed transaction.
 8
 9### Saga Steps
10
11Step 1: Order Service → Create order (status: DRAFT)
12Step 2: Inventory Service → Reserve stock
13  - Success: publish STOCK_RESERVED
14  - Failure: publish STOCK_RESERVATION_FAILED
15Step 3 (if STOCK_RESERVED): Payment Service → Charge payment
16  - Success: publish PAYMENT_CHARGED
17  - Failure: publish PAYMENT_FAILED
18Step 4 (if PAYMENT_CHARGED): Order Service → Confirm order (PENDING → CONFIRMED)
19Step 5 (if PAYMENT_CHARGED): Inventory Service → Commit stock reservation
20
21### Compensating Transactions
22If Step 3 fails (PAYMENT_FAILED):
23- Compensating: Inventory Service → Release reserved stock
24
25If Step 4 fails (cannot confirm order):
26- Compensating: Payment Service → Refund payment
27- Compensating: Inventory Service → Release reserved stock
28
29### Acceptance Criteria
30- AC1: Saga berhasil dalam < 5 detik untuk happy path
31- AC2: Jika payment gagal, stok dikembalikan dalam 30 detik
32- AC3: Tidak ada double charge pada retry
33- AC4: Idempotency: request yang sama dua kali hanya execute once

Bagian Compensating Transactions adalah jantung spec ini: ia menuliskan secara eksplisit bagaimana sistem “membatalkan” langkah yang sudah terlanjur sukses ketika langkah berikutnya gagal — logika yang paling sering salah kalau hanya mengandalkan improvisasi saat coding.


16.12 Contract First untuk Internal API

Untuk internal API baru antar service, selalu mulai dari contract sebelum satu baris kode ditulis. Prompt berikut meminta Claude Code merancang endpoint bulk stock check sekaligus contract test-nya, sehingga dua tim bisa jalan paralel.

text
 1Tim A (Order Service) perlu menambahkan call ke Product Service
 2untuk check bulk stock availability (bukan satu produk, tapi array).
 3
 4Kita belum punya endpoint ini di Product Service.
 5
 6Bantuan yang dibutuhkan:
 71. Draft OpenAPI spec untuk endpoint baru:
 8   POST /internal/v1/products/check-bulk-stock
 9   Input: [{ product_id, quantity }]
10   Output: [{ product_id, available, current_stock }]
11
122. Identifikasi: apakah ini request-response atau event yang lebih cocok?
13
143. Draft contract test yang harus lulus sebelum Order Service
15   bisa start menggunakan endpoint ini
16
174. Backward compatibility considerations jika request/response format
18   perlu berubah di masa depan
19
20Kemudian Tim B (Product Service) bisa implement berdasarkan contract ini,
21sementara Tim A bisa develop dengan mock dari contract.

Pola contract-first inilah yang membuka kerja paralel: begitu contract disepakati, Tim A bisa langsung membangun di atas mock sementara Tim B mengerjakan implementasi asli — keduanya tidak saling menunggu.


16.13 Spec Drift di Multi-Service Environment

Spec drift jauh lebih berbahaya di microservice dibanding monolith karena tidak ada compiler yang menangkapnya. Skenario berikut menggambarkan bagaimana satu ketidakcocokan nama field bisa lolos sampai production.

text
1Scenario: Order Service menggunakan field "total_price" tapi
2Notification Service expect "total_amount_cents".
3
4Di monolith: compile error
5Di microservice: runtime bug yang mungkin baru ketahuan
6                  ketika customer complain email tidak dikirim

Pelajarannya: di microservice, ketidakcocokan schema tidak muncul sebagai error saat build, melainkan sebagai keluhan customer berminggu-minggu kemudian. Karena itu kita butuh deteksi otomatis, seperti script berikut yang mencocokkan field yang dipakai consumer dengan schema resmi.

bash
 1#!/bin/bash
 2# scripts/check-event-contract.sh
 3
 4# Extract all fields used by Notification Service
 5NOTIF_FIELDS=$(grep -r "event\." notification-service/internal/ | \
 6    grep -o 'event\.[A-Za-z_]*' | sort -u)
 7
 8# Check each field exists in event schema
 9SCHEMA_FILE="specs/contracts/order-events.md"
10
11for field in $NOTIF_FIELDS; do
12    field_name="${field#event.}"
13    if ! grep -q "$field_name" "$SCHEMA_FILE"; then
14        echo "❌ Field '$field_name' used by notification-service not found in event schema"
15        exit 1
16    fi
17done
18
19echo "✅ All notification-service fields found in event schema"

Script sederhana ini memindahkan deteksi drift dari “nanti saat ada komplain” menjadi “sekarang di CI”: begitu consumer memakai field yang tidak ada di schema, build gagal dan developer langsung sadar.


16.14 Canary Deployment dengan Contract Verification

Sebelum rollout penuh, verifikasi contract bisa dijadikan gerbang deployment. Pipeline berikut hanya merilis 5% traffic ke versi baru Order Service setelah semua consumer contract terbukti terpenuhi.

yaml
 1# Deployment pipeline
 2stages:
 3  - name: contract-check
 4    run: |
 5      # Verify new Order Service version satisfies all consumer contracts
 6      pact-provider-verifier \
 7        --provider=order-service \
 8        --provider-base-url=http://canary-order-service \
 9        --pact-broker-url=http://pact-broker \
10        --publish-verification-results
11
12  - name: canary-deploy
13    only_if: contract-check == passed
14    run: |
15      # Route 5% traffic to new Order Service
16      kubectl set image deployment/order-service \
17        order-service=order-service:new-version
18      kubectl annotate deployment/order-service \
19        rollout.kubernetes.io/canary-weight=5

Klausa only_if: contract-check == passed adalah pengaman terakhir: versi baru tidak akan pernah menerima traffic produksi jika ada satu saja consumer contract yang tidak terpenuhi.


16.15 Spec untuk Message Queue Configuration

Konfigurasi message queue juga layak di-spec agar tidak jadi “pengetahuan tersembunyi” satu-dua orang. Spec berikut mendokumentasikan partisi, retensi, dan penanganan error untuk topic order-events.

markdown
 1# specs/infrastructure/kafka-config.md
 2
 3## Kafka Topic Configuration — Order Events
 4
 5### Topic: order-events
 6- Partitions: 12 (supports 12 consumers in parallel)
 7- Replication factor: 3 (resilience)
 8- Retention: 7 days (allow consumer lag recovery)
 9- Compaction: none (event log, all events matter)
10- Max message size: 1MB
11
12### Consumer Groups
13- notification-service: consume from offset group "notification"
14- analytics-service: consume from offset group "analytics"
15- Each consumer group has independent offset (one behind doesn't affect other)
16
17### Message Ordering Guarantee
18- Same order_id → same partition (key = order_id)
19- Events for one order are ordered within one partition
20- No global ordering across different orders
21
22### Error Handling
23- Consumer max retries: 3
24- Dead letter topic: order-events-dlq
25- DLQ retention: 30 days (for manual replay/investigation)

Yang paling sering disalahpahami dari konfigurasi ini adalah jaminan ordering: Kafka hanya menjamin urutan per partisi, bukan global — dan spec menuliskannya eksplisit supaya consumer tidak berasumsi salah.


16.16 Testing Multi-Service Spec Compliance

Contract test memverifikasi kontrak, tetapi perilaku end-to-end lintas service tetap butuh integration test. Test berikut memastikan bahwa membatalkan order benar-benar memicu event dan email di lingkungan integrasi.

go
 1// End-to-end contract test (integration environment)
 2// order-service/e2e/cancel_order_multi_service_test.go
 3
 4func TestCancelOrder_MultiService_NotificationSent(t *testing.T) {
 5    // Setup: create a pending order
 6    orderID := createPendingOrder(t)
 7
 8    // When: cancel the order
 9    resp, err := http.NewRequest(http.MethodDelete,
10        baseURL+"/orders/"+orderID.String(), nil)
11    // ... execute request
12
13    // Then: verify Order Service response
14    assert.Equal(t, http.StatusNoContent, resp.StatusCode)
15
16    // Verify: ORDER_CANCELLED event was published to Kafka
17    event := consumeFromKafka(t, "order-events", 5*time.Second)
18    assert.Equal(t, "ORDER_CANCELLED", event.EventType)
19    assert.Equal(t, orderID.String(), event.Payload.OrderID)
20
21    // XAC1: Email sent within 60 seconds (via Notification Service)
22    // Note: requires notification service to be running in test env
23    emailSent := waitForEmail(t, testEmail, 60*time.Second)
24    assert.True(t, emailSent, "cancellation email should be sent within 60 seconds")
25}

Test ini secara langsung memetakan ke XAC1 di spec multi-service: ia tidak hanya mengecek response 204, tetapi juga membuktikan event terbit ke Kafka dan email benar-benar terkirim dalam 60 detik — verifikasi yang tidak bisa dijangkau contract test.


16.17 Dokumentasi Inter-Service API

Contract yang baik juga butuh dokumentasi operasional: kapan dipanggil, bagaimana kalau gagal, berapa SLA-nya. Prompt berikut meminta Claude Code melengkapi dokumentasi endpoint cek stok dengan aspek-aspek itu.

text
 1Bantuan membuat dokumentasi untuk API contract antara:
 2Order Service (provider) → Product Service (consumer)
 3
 4Endpoint yang sudah ada:
 5GET /internal/v1/products/{id}/stock
 6
 7Buat dokumentasi yang mencakup:
 81. Use case: kapan Order Service memanggil endpoint ini?
 92. Authentication: cara Product Service verify caller adalah Order Service
103. Error handling: apa yang Order Service lakukan jika:
11   - Product Service return 503
12   - Network timeout
13   - Product tidak ditemukan
144. SLA: berapa long timeout yang acceptable?
155. Fallback: apakah ada fallback jika Product Service down?
16
17Format: bagian dari specs/contracts/order-product-sync.md

Dokumentasi yang mencakup error handling, SLA, dan fallback inilah yang membedakan contract “sekadar ada” dari contract yang benar-benar bisa diandalkan saat service downstream bermasalah di tengah malam.


16.18 Tips & Gotchas

💡 Tip 1: Contract-first sebelum code untuk inter-service API

Ketika dua tim perlu berkomunikasi via API, definisikan contract dulu sebelum implementasi. Mock server dari contract memungkinkan kedua tim develop paralel.

💡 Tip 2: Versioning dari hari pertama

Jangan tunggu sampai perlu breaking change untuk mulai version. Mulai dengan v1.0 dari awal, sehingga proses sudah ada ketika dibutuhkan.

💡 Tip 3: Consumer-driven contract lebih aman dari provider-driven

Provider tahu apa yang dia bisa berikan. Consumer tahu apa yang dia butuhkan. Consumer-driven contract lebih likely menghasilkan API yang benar-benar digunakan.

💡 Tip 4: Event schema di-code review seperti API

Perubahan event schema adalah breaking change jika consumer tidak bisa handle. Treat event schema change dengan severity yang sama seperti API change.

⚠️ Gotcha 1: “Schema evolution” lebih sulit dari “API evolution”

API change bisa di-versioned dengan path (/v1, /v2). Event schema change memerlukan semua consumer update sebelum producer bisa drop old format. Plan early.

⚠️ Gotcha 2: Asumsi ordering yang tidak dijamin

Kafka guarantees ordering per partition, bukan global. Jika consumer asumsi ORDER_CREATED selalu sebelum ORDER_CANCELLED, ini bisa break jika partisi berbeda.

⚠️ Gotcha 3: Contract testing tidak replace integration testing

Contract test verify contract, bukan actual behavior. Integration test masih diperlukan untuk verify end-to-end behavior dalam real environment.

⚠️ Gotcha 4: Secret sharing antar service via environment variable, bukan spec

Spec mendefinisikan authentication mechanism (mTLS, X-Service-Token), bukan nilai token-nya. Nilai token adalah secrets — tidak boleh di spec.


16.19 Contoh Spec Microservice yang Lengkap

Sebagai penutup bagian teknis, mari lihat satu contract lengkap yang menyatukan event, required fields, reliability, dan versioning dalam satu file. Spec berikut mendefinisikan kontrak Order Service → Notification Service secara utuh.

markdown
 1# specs/contracts/order-notification-contract.md
 2
 3## Contract: Order Service → Notification Service
 4
 5### Via Kafka (primary)
 6
 7Order Service publishes events yang dikonsumsi Notification Service:
 8
 9#### ORDER_CREATED
10Trigger: customer berhasil checkout
11Notification: email konfirmasi order
12
13Required fields untuk Notification Service:
14- payload.order_id
15- payload.user_id (untuk lookup email)
16- payload.order_number (untuk display)
17- payload.total_amount_cents (untuk display)
18- payload.items[].product_name, quantity, price_cents
19- payload.estimated_delivery (string, for display)
20
21#### ORDER_CANCELLED
22Trigger: customer atau admin cancel order
23Notification: email konfirmasi cancel
24
25Required fields:
26- payload.order_id
27- payload.user_id
28- payload.order_number
29- payload.cancelled_at
30- payload.refund_amount_cents (jika ada refund)
31
32### Reliability Contract
33- Notification Service HARUS idempotent (re-consume event tidak double-send email)
34- Jika Notification Service down: event di-retry selama 7 hari via Kafka retention
35- Tidak ada SLA untuk email delivery — best effort
36
37### Versioning
38- Current event version: ORDER_CREATED v1.2, ORDER_CANCELLED v1.1
39- Breaking changes require 30-day deprecation period
40- Schema changelog: specs/contracts/changelog.md

Spec ini menjadi rujukan tunggal: developer Notification Service tahu persis field mana yang dijamin ada, tim Order Service tahu field mana yang tidak boleh dihapus tanpa versioning, dan keduanya sepakat bahwa email adalah best-effort — semuanya tertulis, bukan tersirat.


16.20 Ringkasan

SDD di microservice environment membutuhkan tiga jenis contract specification: synchronous HTTP API contract, asynchronous event contract, dan data/query API contract.

Consumer-Driven Contract Testing adalah approach yang paling robust — consumer mendefinisikan apa yang dibutuhkan, provider memverifikasi bahwa dia memenuhi semua kebutuhan consumer.

Multi-service spec harus mencakup: service interaction flow, cross-service acceptance criteria, event schema dengan versioning policy, dan reliability contract (apa yang terjadi jika salah satu service down).

Bahaya terbesar adalah spec drift — ketika event schema di producer tidak sinkron dengan expectation consumer. Automated contract compatibility check di CI adalah pertahanan pertama.

Di artikel berikutnya, kita bahas Spec Drift Detection — bagaimana mendeteksi dan mencegah ketika kode mulai keluar dari spesifikasi, di satu service maupun lintas service.

Artikel Terkait

💬 Komentar