OpenAPI Contract Golang E-Commerce: API Documentation dari Spec SDD
Buat OpenAPI contract lengkap untuk Santekno Shop dari spec SDD. Swagger/OpenAPI 3.0, code generation, mock server, dan contract testing untuk Go e-commerce.
OpenAPI Contract untuk Semua Service Santekno Shop
Membangun OpenAPI contract untuk e-commerce Golang berarti mendefinisikan API sebelum kode ditulis. OpenAPI spec adalah kontrak resmi antara backend dan frontend — frontend bisa mulai develop sambil kita implement backend. Di artikel ini, kita convert spec dari artikel pertama menjadi OpenAPI 3.0 yang lengkap, lalu memanfaatkannya untuk code generation, mock server, dan contract testing.
Contract-first bukan sekadar dokumentasi. Ia menjadi single source of truth yang mengikat backend, frontend, dan CI pada satu bentuk API yang sama — sehingga integrasi berhenti menjadi sumber bug yang tak berkesudahan.
03.01 Kenapa OpenAPI Contract Penting
Sebelum masuk teknis, penting memahami masalah yang dipecahkan contract. Perbandingan berikut menunjukkan kekacauan tanpa contract versus ketertiban dengan OpenAPI.
1TANPA API CONTRACT:
2 Backend developer: "Saya return snake_case"
3 Frontend developer: "Loh, saya expect camelCase"
4 → Wasted integration time
5
6 Backend: "Error message-nya 'order.not_found'"
7 Frontend: "Saya kode untuk 'ORDER_NOT_FOUND'"
8 → Bug di production
9
10DENGAN OpenAPI CONTRACT:
11 Satu source of truth untuk API shape
12 Frontend bisa generate TypeScript types dari spec
13 Backend bisa validate response against spec
14 Mock server dari spec → frontend develop paralel
15 Contract testing → integrations yang reliable
16
17Keuntungan spesifik untuk Santekno Shop:
18 → spec SDD → OpenAPI spec → Go server code
19 → CI validates response format against OpenAPI
20 → Frontend team tidak perlu tunggu backend selesaiPoin terpentingnya: contract memindahkan penemuan mismatch (snake_case vs camelCase, kode error berbeda) dari fase integrasi ke fase desain — jauh sebelum kode bertemu di production.
03.02 Setup Tooling
Untuk bekerja dengan OpenAPI, kita butuh beberapa tool. Perintah berikut memasang generator kode, validator, mock server, dan generator TypeScript sekaligus.
1# Tools yang digunakan:
2
3# 1. oapi-codegen: generate Go server code dari OpenAPI
4go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
5
6# 2. swagger-cli: validate OpenAPI spec
7npm install -g @apidevtools/swagger-cli
8
9# 3. prism: mock server dari OpenAPI
10npm install -g @stoplight/prism-cli
11
12# 4. openapi-typescript: generate TypeScript types (untuk frontend)
13npm install -g openapi-typescript
14
15# Verify:
16oapi-codegen --version
17swagger-cli --version
18prism --versionEmpat tool ini menutup seluruh siklus contract-first: menulis, memvalidasi, men-generate kode, dan menyediakan mock. Menjalankan blok verifikasi memastikan semuanya siap sebelum menulis spec.
03.03 OpenAPI 3.0 Spec: Auth Endpoints
Kita mulai dengan endpoint autentikasi. Spec YAML berikut mendefinisikan security scheme, schema umum, dan endpoint register/login lengkap dengan contoh response.
1# api/openapi/santekno-shop.yaml
2openapi: '3.0.0'
3info:
4 title: Santekno Shop API
5 version: '1.0.0'
6 description: |
7 B2C E-Commerce Platform Indonesia.
8 All monetary values are in IDR cents (1 IDR = 100 cents).
9 All IDs are UUIDs.
10
11servers:
12 - url: http://localhost:8080/api/v1
13 description: Development
14 - url: https://staging.santekno.com/api/v1
15 description: Staging
16
17components:
18 securitySchemes:
19 BearerAuth:
20 type: http
21 scheme: bearer
22 bearerFormat: JWT
23
24 schemas:
25 ErrorResponse:
26 type: object
27 required: [error]
28 properties:
29 error:
30 type: string
31 example: "ORDER_NOT_FOUND"
32 description: Machine-readable error code
33 detail:
34 type: string
35 example: "Order with ID xxx was not found"
36 description: Human-readable description
37
38 PaginationMeta:
39 type: object
40 properties:
41 page: { type: integer, example: 1 }
42 limit: { type: integer, example: 10 }
43 total: { type: integer, example: 100 }
44 total_pages: { type: integer, example: 10 }
45
46 UUID:
47 type: string
48 format: uuid
49 example: "550e8400-e29b-41d4-a716-446655440000"
50
51 AmountIDR:
52 type: integer
53 format: int64
54 description: "Amount in IDR cents. 1 IDR = 100 cents. Rp 50.000 = 5000000"
55 example: 5000000
56
57 UserResponse:
58 type: object
59 properties:
60 id: { $ref: '#/components/schemas/UUID' }
61 email: { type: string, format: email }
62 name: { type: string }
63 created_at: { type: string, format: date-time }
64
65 RegisterRequest:
66 type: object
67 required: [email, password, name]
68 properties:
69 email:
70 type: string
71 format: email
72 example: "user@example.com"
73 password:
74 type: string
75 minLength: 8
76 example: "Secret123"
77 description: "Minimum 8 characters, must contain at least 1 digit"
78 name:
79 type: string
80 minLength: 2
81 maxLength: 100
82 example: "John Doe"
83
84 LoginRequest:
85 type: object
86 required: [email, password]
87 properties:
88 email: { type: string, format: email }
89 password: { type: string }
90
91 TokenResponse:
92 type: object
93 properties:
94 access_token: { type: string }
95 refresh_token: { type: string }
96 expires_in: { type: integer, example: 3600 }
97 token_type: { type: string, example: "Bearer" }
98
99paths:
100 /auth/register:
101 post:
102 tags: [Auth]
103 summary: Register new user
104 operationId: registerUser
105 requestBody:
106 required: true
107 content:
108 application/json:
109 schema: { $ref: '#/components/schemas/RegisterRequest' }
110 responses:
111 '201':
112 description: User registered successfully
113 content:
114 application/json:
115 schema: { $ref: '#/components/schemas/UserResponse' }
116 '400':
117 description: Validation error
118 content:
119 application/json:
120 schema: { $ref: '#/components/schemas/ErrorResponse' }
121 '409':
122 description: Email already registered
123 content:
124 application/json:
125 schema: { $ref: '#/components/schemas/ErrorResponse' }
126 example: { error: "EMAIL_ALREADY_REGISTERED" }
127
128 /auth/login:
129 post:
130 tags: [Auth]
131 summary: Login and get tokens
132 operationId: loginUser
133 requestBody:
134 required: true
135 content:
136 application/json:
137 schema: { $ref: '#/components/schemas/LoginRequest' }
138 responses:
139 '200':
140 description: Login successful
141 content:
142 application/json:
143 schema: { $ref: '#/components/schemas/TokenResponse' }
144 '401':
145 description: Invalid credentials
146 content:
147 application/json:
148 schema: { $ref: '#/components/schemas/ErrorResponse' }
149 example: { error: "INVALID_CREDENTIALS" }
150 '429':
151 description: Too many attempts
152 content:
153 application/json:
154 schema: { $ref: '#/components/schemas/ErrorResponse' }
155 example: { error: "TOO_MANY_ATTEMPTS", detail: "retry_after: 900" }Setiap status code di spec ini persis mencerminkan AC dari spec SDD: 409 untuk email terdaftar, 429 untuk rate limit. Skema AmountIDR dan ErrorResponse yang reusable memastikan konsistensi di seluruh endpoint berikutnya.
03.04 OpenAPI Spec: Order Endpoints
Endpoint order lebih kompleks karena melibatkan array item dan berbagai error bisnis. Spec YAML berikut mendefinisikan schema order beserta endpoint place, list, dan cancel.
1# (lanjutan api/openapi/santekno-shop.yaml)
2
3components:
4 schemas:
5 OrderItem:
6 type: object
7 properties:
8 product_id: { $ref: '#/components/schemas/UUID' }
9 product_name: { type: string }
10 unit_price_idr: { $ref: '#/components/schemas/AmountIDR' }
11 quantity: { type: integer, minimum: 1 }
12 subtotal_idr: { $ref: '#/components/schemas/AmountIDR' }
13
14 OrderStatus:
15 type: string
16 enum: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
17
18 OrderResponse:
19 type: object
20 properties:
21 id: { $ref: '#/components/schemas/UUID' }
22 status: { $ref: '#/components/schemas/OrderStatus' }
23 items:
24 type: array
25 items: { $ref: '#/components/schemas/OrderItem' }
26 total_amount_idr: { $ref: '#/components/schemas/AmountIDR' }
27 payment_method: { type: string, enum: [BANK_TRANSFER, QRIS, CREDIT_CARD] }
28 payment_url: { type: string, format: uri, nullable: true }
29 created_at: { type: string, format: date-time }
30 updated_at: { type: string, format: date-time }
31
32 PlaceOrderRequest:
33 type: object
34 required: [items, shipping_address_id, payment_method]
35 properties:
36 items:
37 type: array
38 minItems: 1
39 maxItems: 10
40 items:
41 type: object
42 required: [product_id, quantity]
43 properties:
44 product_id: { $ref: '#/components/schemas/UUID' }
45 quantity:
46 type: integer
47 minimum: 1
48 maximum: 100
49 shipping_address_id: { $ref: '#/components/schemas/UUID' }
50 payment_method:
51 type: string
52 enum: [BANK_TRANSFER, QRIS, CREDIT_CARD]
53
54 OrderListResponse:
55 type: object
56 properties:
57 data:
58 type: array
59 items: { $ref: '#/components/schemas/OrderResponse' }
60 pagination: { $ref: '#/components/schemas/PaginationMeta' }
61
62paths:
63 /orders:
64 get:
65 tags: [Orders]
66 summary: Get user's orders
67 security: [{ BearerAuth: [] }]
68 operationId: getOrders
69 parameters:
70 - name: page
71 in: query
72 schema: { type: integer, default: 1, minimum: 1 }
73 - name: limit
74 in: query
75 schema: { type: integer, default: 10, minimum: 1, maximum: 100 }
76 - name: status
77 in: query
78 schema: { $ref: '#/components/schemas/OrderStatus' }
79 responses:
80 '200':
81 description: List of orders
82 content:
83 application/json:
84 schema: { $ref: '#/components/schemas/OrderListResponse' }
85 '401':
86 description: Unauthorized
87 content:
88 application/json:
89 schema: { $ref: '#/components/schemas/ErrorResponse' }
90 example: { error: "UNAUTHORIZED" }
91
92 post:
93 tags: [Orders]
94 summary: Place a new order
95 security: [{ BearerAuth: [] }]
96 operationId: placeOrder
97 requestBody:
98 required: true
99 content:
100 application/json:
101 schema: { $ref: '#/components/schemas/PlaceOrderRequest' }
102 responses:
103 '201':
104 description: Order placed successfully
105 content:
106 application/json:
107 schema: { $ref: '#/components/schemas/OrderResponse' }
108 '400':
109 description: Invalid request
110 content:
111 application/json:
112 schema: { $ref: '#/components/schemas/ErrorResponse' }
113 example: { error: "EMPTY_ORDER" }
114 '404':
115 description: Product not found
116 content:
117 application/json:
118 schema: { $ref: '#/components/schemas/ErrorResponse' }
119 example: { error: "PRODUCT_NOT_FOUND", detail: "product_id: xxx" }
120 '422':
121 description: Insufficient stock
122 content:
123 application/json:
124 schema: { $ref: '#/components/schemas/ErrorResponse' }
125 example: { error: "INSUFFICIENT_STOCK", detail: "product_id: xxx" }
126
127 /orders/{orderId}:
128 delete:
129 tags: [Orders]
130 summary: Cancel an order
131 security: [{ BearerAuth: [] }]
132 operationId: cancelOrder
133 parameters:
134 - name: orderId
135 in: path
136 required: true
137 schema: { $ref: '#/components/schemas/UUID' }
138 responses:
139 '204':
140 description: Order cancelled successfully
141 '404':
142 description: Order not found
143 content:
144 application/json:
145 schema: { $ref: '#/components/schemas/ErrorResponse' }
146 example: { error: "ORDER_NOT_FOUND" }
147 '409':
148 description: Order cannot be cancelled
149 content:
150 application/json:
151 schema: { $ref: '#/components/schemas/ErrorResponse' }
152 examples:
153 not_cancellable:
154 value: { error: "ORDER_NOT_CANCELLABLE" }
155 window_expired:
156 value: { error: "CANCEL_WINDOW_EXPIRED" }Batasan minItems: 1 dan maxItems: 10 pada items, serta maximum: 100 pada quantity, menegakkan business rule dari spec langsung di level contract. Validasi ini otomatis berlaku untuk mock server maupun kode yang di-generate.
03.05 OpenAPI Spec: Product Endpoints
Endpoint produk melengkapi katalog dengan filter dan search. Spec YAML berikut mendefinisikan schema produk serta endpoint list dan detail.
1components:
2 schemas:
3 ProductResponse:
4 type: object
5 properties:
6 id: { $ref: '#/components/schemas/UUID' }
7 name: { type: string }
8 description: { type: string, nullable: true }
9 price_idr: { $ref: '#/components/schemas/AmountIDR' }
10 stock: { type: integer, minimum: 0 }
11 category:
12 type: object
13 properties:
14 id: { $ref: '#/components/schemas/UUID' }
15 name: { type: string }
16 images:
17 type: array
18 items: { type: string, format: uri }
19 maxItems: 10
20 is_active: { type: boolean }
21 created_at: { type: string, format: date-time }
22
23 ProductListResponse:
24 type: object
25 properties:
26 data:
27 type: array
28 items: { $ref: '#/components/schemas/ProductResponse' }
29 pagination: { $ref: '#/components/schemas/PaginationMeta' }
30
31paths:
32 /products:
33 get:
34 tags: [Products]
35 summary: List products with filters
36 operationId: listProducts
37 parameters:
38 - name: q
39 in: query
40 description: Search query (full-text search)
41 schema: { type: string }
42 - name: category
43 in: query
44 schema: { $ref: '#/components/schemas/UUID' }
45 - name: min_price
46 in: query
47 description: Minimum price in IDR cents
48 schema: { type: integer, minimum: 0 }
49 - name: max_price
50 in: query
51 description: Maximum price in IDR cents
52 schema: { type: integer, minimum: 0 }
53 - name: page
54 in: query
55 schema: { type: integer, default: 1 }
56 - name: limit
57 in: query
58 schema: { type: integer, default: 10, maximum: 50 }
59 responses:
60 '200':
61 description: List of products
62 content:
63 application/json:
64 schema: { $ref: '#/components/schemas/ProductListResponse' }
65
66 /products/{productId}:
67 get:
68 tags: [Products]
69 summary: Get product detail
70 operationId: getProduct
71 parameters:
72 - name: productId
73 in: path
74 required: true
75 schema: { $ref: '#/components/schemas/UUID' }
76 responses:
77 '200':
78 description: Product detail
79 content:
80 application/json:
81 schema: { $ref: '#/components/schemas/ProductResponse' }
82 '404':
83 description: Product not found
84 content:
85 application/json:
86 schema: { $ref: '#/components/schemas/ErrorResponse' }
87 example: { error: "NOT_FOUND" }Endpoint produk sengaja tidak memakai security karena katalog bisa dibaca publik — konsisten dengan aturan auth di spec. Reuse AmountIDR dan PaginationMeta di sini menunjukkan nilai schema komponen yang terpusat.
03.06 Validasi OpenAPI Spec
Spec yang tidak valid akan menyesatkan semua tool di hilir. Perintah berikut memvalidasi spec dan memasangnya sebagai pre-commit hook agar drift tertangkap sedini mungkin.
1# Validate spec sebelum commit
2swagger-cli validate api/openapi/santekno-shop.yaml
3
4# Expected output: api/openapi/santekno-shop.yaml is valid
5# Jika ada error: fix sebelum commit
6
7# Tambahkan ke pre-commit:
8# .pre-commit-config.yaml
9repos:
10 - repo: local
11 hooks:
12 - id: validate-openapi
13 name: Validate OpenAPI Spec
14 language: system
15 entry: swagger-cli validate api/openapi/santekno-shop.yaml
16 files: 'api/openapi/.*\.yaml'Memasang validasi sebagai pre-commit hook berarti spec yang rusak tidak akan pernah masuk ke repository. Ini gerbang paling awal dalam rantai pencegahan drift.
03.07 Code Generation dari OpenAPI
Kekuatan sesungguhnya OpenAPI muncul saat spec menghasilkan kode Go otomatis. Perintah dan config berikut menjalankan oapi-codegen untuk menghasilkan types dan server interface.
1# oapi-codegen config
2# api/openapi/gen-config.yaml
3
4package: api
5generate:
6 - types # Generate Go types dari schemas
7 - server # Generate Echo server interface
8 - client # Generate HTTP client (untuk testing)
9 - spec # Embed spec in binary
10
11output: internal/api/generated.goDengan konfigurasi generator di atas, perintah berikut menjalankan oapi-codegen untuk menghasilkan kode server dari spesifikasi.
1# Generate code:
2oapi-codegen \
3 --config api/openapi/gen-config.yaml \
4 api/openapi/santekno-shop.yaml
5
6# Output: internal/api/generated.go
7# Contains:
8# - RegisterRequest, LoginRequest, TokenResponse structs
9# - OrderResponse, PlaceOrderRequest structs
10# - ProductResponse, ProductListResponse structs
11# - ServerInterface (yang diimplementasi handler)Config memerintahkan generator menghasilkan empat artefak sekaligus: types, server interface, client, dan embedded spec. Hasil generate-nya berupa kode Go seperti berikut.
1// internal/api/generated.go (sample generated code)
2// JANGAN edit manual — di-generate ulang dari spec
3
4// RegisterRequest defines model for RegisterRequest.
5type RegisterRequest struct {
6 Email string `json:"email"`
7 Name string `json:"name"`
8 Password string `json:"password"`
9}
10
11// OrderResponse defines model for OrderResponse.
12type OrderResponse struct {
13 CreatedAt time.Time `json:"created_at"`
14 Id uuid.UUID `json:"id"`
15 Items []OrderItem `json:"items"`
16 PaymentMethod string `json:"payment_method"`
17 PaymentUrl *string `json:"payment_url"`
18 Status OrderStatus `json:"status"`
19 TotalAmountIdr int64 `json:"total_amount_idr"`
20 UpdatedAt time.Time `json:"updated_at"`
21}Perhatikan TotalAmountIdr int64 — konvensi int64 cents dari spec otomatis muncul di kode Go. Karena ServerInterface juga di-generate, handler yang tidak sesuai contract akan gagal compile: contract enforcement di tingkat compiler.
03.08 Mock Server dengan Prism
Frontend tak perlu menunggu backend selesai. Perintah berikut menyalakan mock server dari spec, lengkap dengan cara memicu skenario sukses maupun error.
1# Start mock server dari OpenAPI spec
2prism mock api/openapi/santekno-shop.yaml --port 4010
3
4# Mock server sekarang available di localhost:4010
5# Frontend bisa develop sambil backend di-implement
6
7# Test mock server:
8curl -X POST http://localhost:4010/api/v1/auth/register \
9 -H "Content-Type: application/json" \
10 -d '{"email": "test@example.com", "password": "Secret123", "name": "Test User"}'
11
12# Response: 201 Created dengan generated fake data
13# {
14# "id": "550e8400-e29b-41d4-a716-446655440000",
15# "email": "test@example.com",
16# "name": "Test User",
17# "created_at": "2026-07-20T10:00:00Z"
18# }
19
20# Test error scenario dengan prefer header:
21curl -X POST http://localhost:4010/api/v1/auth/register \
22 -H "Content-Type: application/json" \
23 -H "Prefer: code=409" \
24 -d '{"email": "existing@example.com"}'
25# Response: 409 ConflictDengan header Prefer: code=409, frontend bahkan bisa menguji jalur error tanpa backend sama sekali. Mock server memungkinkan kedua tim bekerja paralel penuh — penghemat waktu terbesar dari contract-first.
03.09 Contract Testing
Setelah backend diimplementasi, kita perlu memastikan responsenya benar-benar sesuai contract. Kode Go berikut memuat OpenAPI spec dan memverifikasi response endpoint register terhadapnya.
1// internal/api/contract_test.go
2// Verify response format sesuai OpenAPI spec
3
4package api_test
5
6import (
7 "encoding/json"
8 "net/http/httptest"
9 "strings"
10 "testing"
11
12 "github.com/getkin/kin-openapi/openapi3"
13 "github.com/stretchr/testify/assert"
14)
15
16func TestContractCompliance(t *testing.T) {
17 // Load OpenAPI spec
18 loader := openapi3.NewLoader()
19 doc, err := loader.LoadFromFile("../../api/openapi/santekno-shop.yaml")
20 if err != nil {
21 t.Fatal(err)
22 }
23
24 if err := doc.Validate(loader.Context); err != nil {
25 t.Fatal("Invalid OpenAPI spec:", err)
26 }
27
28 // Test: POST /auth/register
29 t.Run("register returns 201 matching schema", func(t *testing.T) {
30 e := setupTestServer()
31 req := httptest.NewRequest("POST", "/api/v1/auth/register",
32 strings.NewReader(`{"email":"test@example.com","password":"Secret123","name":"Test"}`))
33 req.Header.Set("Content-Type", "application/json")
34 rec := httptest.NewRecorder()
35 e.ServeHTTP(rec, req)
36
37 // Verify status
38 assert.Equal(t, 201, rec.Code)
39
40 // Validate response against OpenAPI schema
41 var body map[string]interface{}
42 json.NewDecoder(rec.Body).Decode(&body)
43
44 // body must have: id, email, name, created_at
45 assert.NotEmpty(t, body["id"])
46 assert.Equal(t, "test@example.com", body["email"])
47 assert.NotEmpty(t, body["created_at"])
48
49 // token MUST NOT be in response (per spec + security)
50 assert.Nil(t, body["access_token"])
51 })
52}Test ini menegakkan requirement halus dari spec: register tidak boleh mengembalikan token (assert.Nil(t, body["access_token"])). Contract testing menangkap pelanggaran semacam ini yang mudah lolos dari review manual.
03.10 OpenAPI di CI Pipeline
Contract harus ditegakkan otomatis di setiap PR. Konfigurasi CI berikut memvalidasi spec dan memastikan kode yang di-generate selalu sinkron dengan spec.
1# .github/workflows/ci.yml (tambahkan ke pipeline)
2
3- name: Validate OpenAPI spec
4 run: swagger-cli validate api/openapi/santekno-shop.yaml
5
6- name: Verify generated code is up-to-date
7 run: |
8 oapi-codegen --config api/openapi/gen-config.yaml api/openapi/santekno-shop.yaml
9 # Check jika ada diff (berarti generated code tidak di-update)
10 if ! git diff --quiet internal/api/generated.go; then
11 echo "::error::Generated API code is out of sync with OpenAPI spec"
12 echo "Run: oapi-codegen --config api/openapi/gen-config.yaml ..."
13 exit 1
14 fiLangkah kedua mencegah drift paling umum: spec diperbarui tapi kode generate lupa di-regenerate. Jika ada diff, CI langsung memblokir merge — spec dan kode dijamin selalu sinkron.
03.11 TypeScript Types Generation
Frontend juga butuh type-safety dari contract yang sama. Perintah berikut men-generate TypeScript types langsung dari OpenAPI spec.
1# Generate TypeScript types untuk frontend team
2openapi-typescript api/openapi/santekno-shop.yaml \
3 --output frontend/src/types/api.ts
4
5# Generated types:
6# export interface RegisterRequest {
7# email: string;
8# name: string;
9# password: string;
10# }
11#
12# export interface OrderResponse {
13# id: string; // UUID
14# status: "PENDING" | "CONFIRMED" | "SHIPPED" | "DELIVERED" | "CANCELLED";
15# total_amount_idr: number; // int64 cents
16# }
17
18# Frontend package.json script:
19# "gen:types": "openapi-typescript ../api/openapi/santekno-shop.yaml -o src/types/api.ts"Dengan types di-generate dari spec yang sama dengan backend, frontend dan backend mustahil melenceng: satu perubahan spec langsung memperbarui kedua sisi. Type-safety end-to-end tanpa duplikasi definisi.
03.12 API Versioning Strategy
API akan berevolusi, dan kita butuh aturan perubahan yang jelas. Panduan berikut menetapkan apa yang termasuk breaking change dan bagaimana strategi versioning-nya.
1Santekno Shop API versioning:
2
3URL versioning: /api/v1/
4 Pilihan paling explicit dan easiest untuk proxy/gateway
5
6Breaking change policy:
7 Non-breaking changes: dapat ke v1 langsung
8 + Add optional field ke response
9 + Add optional query param
10 + Add new endpoint
11
12 Breaking changes: require v2
13 - Remove field dari response
14 - Change field type
15 - Change error format
16 - Rename endpoint
17
18V2 strategy (ketika diperlukan):
19 /api/v2/ untuk endpoint baru
20 /api/v1/ tetap di-maintain selama 6 bulan
21 Deprecation header di v1 response
22
23Saat ini: v1 only. V2 tidak perlu untuk saat ini.Membedakan breaking dari non-breaking secara eksplisit mencegah kejutan bagi konsumen API. Selama perubahan hanya menambah field opsional atau endpoint baru, klien lama tetap aman tanpa perlu versi baru.
03.13 Webhook Spec
Webhook payment perlu didokumentasikan dengan requirement keamanannya. Spec YAML berikut mendefinisikan endpoint webhook lengkap dengan header signature dan perilaku idempoten.
1# Tambahkan ke santekno-shop.yaml
2
3paths:
4 /webhooks/payment:
5 post:
6 tags: [Webhooks]
7 summary: Handle payment webhook
8 description: |
9 Receives payment status updates from payment gateway.
10 Request must include HMAC-SHA256 signature in X-Signature header.
11 Idempotent: duplicate requests (same external_payment_id) return 200.
12 operationId: handlePaymentWebhook
13 parameters:
14 - name: X-Signature
15 in: header
16 required: true
17 schema: { type: string }
18 description: HMAC-SHA256 signature of request body
19 requestBody:
20 required: true
21 content:
22 application/json:
23 schema:
24 type: object
25 required: [order_id, payment_id, status, amount_idr, signature]
26 properties:
27 order_id: { $ref: '#/components/schemas/UUID' }
28 payment_id: { type: string, example: "pay_xxx123" }
29 status:
30 type: string
31 enum: [PAID, FAILED]
32 amount_idr: { $ref: '#/components/schemas/AmountIDR' }
33 responses:
34 '200':
35 description: Webhook processed (including idempotent duplicates)
36 '401':
37 description: Invalid signature
38 content:
39 application/json:
40 schema: { $ref: '#/components/schemas/ErrorResponse' }
41 example: { error: "INVALID_SIGNATURE" }Dengan mendokumentasikan X-Signature sebagai header wajib dan menyatakan perilaku idempoten di deskripsi, requirement keamanan menjadi terlihat di contract — bukan tersembunyi di kode. Tim integrator tahu persis apa yang harus mereka kirim.
03.14 API Documentation Website
Contract yang sama bisa menjadi dokumentasi interaktif. Perintah berikut menyajikan spec sebagai website dokumentasi lewat Redoc atau Swagger UI.
1# Setup Swagger UI untuk API documentation
2# Bisa menggunakan redoc atau swagger-ui
3
4# Option 1: Redoc (lebih clean)
5docker run -p 8081:80 \
6 -v $(pwd)/api/openapi:/usr/share/nginx/html/openapi \
7 -e SPEC_URL=/openapi/santekno-shop.yaml \
8 redocly/redoc
9
10# Option 2: Swagger UI
11docker run -p 8081:8080 \
12 -e SWAGGER_JSON=/openapi/santekno-shop.yaml \
13 -v $(pwd)/api/openapi:/openapi \
14 swaggerapi/swagger-uiKarena dokumentasi di-render dari spec yang sama, ia tidak akan pernah basi: setiap update contract otomatis memperbarui website. Tidak ada lagi dokumentasi API yang berbeda dari perilaku sebenarnya.
03.15 Spec Drift Prevention
Untuk menjaga spec, kode, dan test selalu selaras, kita satukan pengecekan dalam satu script. Script berikut memvalidasi spec, memastikan kode sinkron, dan menjalankan contract test sekaligus.
1# Script untuk cek spec vs implementasi alignment
2# scripts/check-spec-drift.sh
3
4# 1. Validate spec valid
5swagger-cli validate api/openapi/santekno-shop.yaml || exit 1
6
7# 2. Check generated code up-to-date
8oapi-codegen --config api/openapi/gen-config.yaml api/openapi/santekno-shop.yaml
9git diff --quiet internal/api/generated.go || {
10 echo "Generated code not in sync with spec"
11 exit 1
12}
13
14# 3. Run contract tests
15go test ./internal/api/... -run TestContractCompliance
16
17echo "✅ Spec alignment check passed"Satu script yang menggabungkan tiga pengecekan ini menjadi definisi tunggal “spec selaras”. Dijalankan di pre-commit dan CI, ia menutup celah drift dari tiga arah sekaligus.
03.16 OpenAPI Extension untuk Santekno Shop
OpenAPI mengizinkan metadata kustom lewat extension x-. Spec berikut menautkan setiap endpoint kembali ke spec SDD dan AC yang mendasarinya.
1# Custom extensions (x-) untuk additional metadata
2paths:
3 /orders:
4 post:
5 x-spec-file: ".specify/features/place-order/spec.md" # link ke SDD spec
6 x-acceptance-criteria:
7 - AC1
8 - AC2
9 - AC3
10 - AC4
11 - AC5
12 x-implementation-status: "complete" # atau "planned", "in-progress"
13 x-since-version: "1.0.0"Extension ini menciptakan rantai telusur: kebutuhan bisnis → spec SDD → OpenAPI contract → implementasi. Saat audit, kita bisa langsung tahu AC mana yang menjadi dasar sebuah endpoint.
03.17 API Rate Limits Documentation
Rate limit perlu terlihat oleh konsumen API. Spec berikut mendokumentasikan header rate limit pada response login, termasuk Retry-After saat limit terlampaui.
1# Tambahkan rate limit info ke OpenAPI spec via headers
2paths:
3 /auth/login:
4 post:
5 responses:
6 '200':
7 headers:
8 X-RateLimit-Limit:
9 schema: { type: integer }
10 description: Max requests per window (5)
11 X-RateLimit-Remaining:
12 schema: { type: integer }
13 description: Remaining requests in window
14 X-RateLimit-Reset:
15 schema: { type: integer }
16 description: Unix timestamp when window resets
17 '429':
18 headers:
19 Retry-After:
20 schema: { type: integer }
21 description: Seconds to wait before retry (900)Mendokumentasikan header seperti X-RateLimit-Remaining membantu klien berperilaku baik: mereka bisa memperlambat request sebelum kena 429. Rate limit menjadi kontrak, bukan kejutan.
03.18 Pagination Standard
Pagination harus seragam di semua endpoint list. Spec berikut mendefinisikan parameter pagination yang reusable agar tidak ada variasi antar endpoint.
1# Consistent pagination across all list endpoints
2
3components:
4 parameters:
5 PageParam:
6 name: page
7 in: query
8 schema: { type: integer, default: 1, minimum: 1 }
9 LimitParam:
10 name: limit
11 in: query
12 schema: { type: integer, default: 10, minimum: 1, maximum: 100 }
13
14# Usage:
15paths:
16 /orders:
17 get:
18 parameters:
19 - $ref: '#/components/parameters/PageParam'
20 - $ref: '#/components/parameters/LimitParam'
21 - name: status
22 in: query
23 schema: { $ref: '#/components/schemas/OrderStatus' }Dengan mendefinisikan PageParam dan LimitParam sekali lalu me-reuse via $ref, semua endpoint list otomatis punya default dan batas yang sama. Konsistensi pagination terjaga tanpa duplikasi.
03.19 Changelog Spec
Riwayat perubahan API sebaiknya hidup di dalam spec itu sendiri. Extension berikut menyimpan changelog terstruktur di dalam blok info.
1# Maintain changelog di OpenAPI spec
2
3info:
4 title: Santekno Shop API
5 version: '1.0.0'
6 x-changelog:
7 - version: "1.0.0"
8 date: "2026-07-01"
9 changes:
10 - "Initial release"
11 - "Auth: register, login, refresh token"
12 - "Orders: place, cancel, list"
13 - "Products: list, search, detail"
14 - "Webhooks: payment callback"Menyimpan changelog di dalam spec berarti riwayat versi ikut ter-version bersama contract di git. Tidak ada lagi changelog terpisah yang mudah lupa diperbarui.
03.20 Ringkasan: Contract Sebelum Code
OpenAPI spec yang kita buat adalah jembatan antara spec SDD (artikel pertama) dan implementasi Go (artikel-artikel berikutnya).
Yang sudah tersedia sekarang:
- 4 tag groups: Auth, Orders, Products, Webhooks
- 10+ endpoint dengan request/response schema lengkap
- Mock server siap untuk frontend development
- Code generation untuk Go types dan interfaces
- TypeScript types untuk frontend
- Contract testing skeleton
Pipeline CI checks:
- Spec validation (swagger-cli)
- Generated code sync check
- Contract compliance tests
Dengan OpenAPI spec yang solid, tim bisa bekerja paralel: frontend consume mock server, backend implement sesuai contract, dan CI menjaga keduanya tetap selaras. Setiap perubahan perilaku API mengalir melalui rantai ini sehingga drift terdeteksi di level spec sebelum sampai production.
Berikutnya, kita rakit fondasi operasional: CLAUDE.md yang komprehensif, konstitusi proyek, dan CI pipeline awal yang menegakkan semua aturan ini secara otomatis.