OpenAPI Spec dengan Claude Code: Contract-First API Design untuk Golang
Panduan lengkap membuat OpenAPI specification dengan Claude Code menggunakan pendekatan contract-first. Dari feature spec ke OpenAPI YAML, generate Go server code, dan validasi contract antar service.
OpenAPI Spec Pertama dengan Claude: Contract-First API Design
Membuat OpenAPI spec golang dengan Claude Code adalah cara paling efektif untuk menegakkan disiplin contract-first di tim backend. Di banyak tim, API design justru terjadi secara implicit — developer menulis kode, endpoint muncul, dan dokumentasi dibuat setelah fakta. Ini adalah anti-pattern yang mahal: consumer service perlu menunggu implementasi selesai untuk tahu cara menggunakannya, perubahan bisa breaking tanpa notice, dan tidak ada single source of truth untuk API contract.
Contract-first API design membalikkan urutan ini: definisikan API contract lebih dulu dalam OpenAPI specification, review bersama stakeholder, baru implementasikan. Di artikel ini, kita pelajari bagaimana menggunakan Claude Code untuk menghasilkan OpenAPI spec yang production-grade dari feature spec yang sudah kita buat.
08.1 Mengapa Contract-First Lebih dari Sekadar Dokumentasi
Contract-first bukan hanya tentang dokumentasi yang bagus — ada keuntungan engineering yang konkret:
Paralel Development: Tim frontend dan backend bisa bekerja paralel. Frontend mock API dari contract, backend implementasi sesuai contract. Tidak perlu menunggu satu sama lain.
Explicit Breaking Change Detection: Ketika contract berubah, semua consumer langsung tahu — bukan melalui runtime error tapi melalui schema validation.
Testable Contract: Contract yang terdefinisi dalam OpenAPI bisa digunakan untuk automated contract testing antara producer dan consumer.
AI Code Generation yang Lebih Akurat: Dengan OpenAPI contract yang jelas, Claude Code bisa generate handler, middleware, dan test yang lebih tepat sasaran.
08.2 Dari Feature Spec ke OpenAPI: Alur Kerja
Sebelum masuk ke prompt dan kode, penting memahami peta besarnya. Diagram alur berikut menunjukkan bagaimana satu feature spec bercabang menjadi OpenAPI spec, kode server, dan contract test.
1Feature Spec (specs/order/cancel-order.md)
2 │
3 ▼ [Claude Code]
4OpenAPI Spec (api/openapi.yaml)
5 │
6 ├─► Go Server Code (internal/delivery/http/handler/)
7 │ [oapi-codegen atau kin-openapi]
8 │
9 └─► Contract Test (test/contract/)
10 [schemathesis atau dredd]Yang perlu diperhatikan dari alur di atas: OpenAPI spec berada di tengah sebagai hub — kode server dan contract test sama-sama diturunkan darinya, sehingga keduanya dijamin konsisten dengan satu sumber kebenaran.
08.3 Prompt untuk Generate OpenAPI dari Feature Spec
Langkah pertama adalah mengubah feature spec menjadi OpenAPI spec. Prompt berikut memberi Claude aturan ketat agar setiap AC dan error case terpetakan ke endpoint dan response schema.
1Berdasarkan feature spec berikut, hasilkan OpenAPI 3.0.3 specification
2dalam format YAML.
3
4Requirements:
51. Setiap AC yang menyebutkan HTTP endpoint harus ter-representasikan
62. Setiap error case harus punya response schema dengan field error_code dan message
73. Gunakan reusable components untuk schema yang dipakai di banyak tempat
84. Include security scheme untuk JWT Bearer token
95. Response schema harus konsisten dengan konvensi: { data: ... } untuk success,
10 { error_code: ..., message: ... } untuk error
116. Include example values yang realistic (bukan "string" tapi "ORD-20250715-ABC123")
12
13Tambahan konteks:
14- Base URL: /api/v1
15- Authentication: Bearer JWT (semua endpoint kecuali yang public)
16- Semua ID adalah UUID v4
17
18---
19@specs/order/cancel-order.mdAturan nomor 6 tentang example yang realistic sering diremehkan padahal krusial: example seperti ORD-20250715-ABC123 membuat spec langsung berguna untuk mock server dan dokumentasi, sementara "string" tidak memberi informasi apa pun ke consumer.
08.4 Contoh OpenAPI Spec: Cancel Order Endpoint
Dari prompt di atas, Claude menghasilkan OpenAPI spec seperti berikut untuk endpoint cancel order — lengkap dengan semua response code dan reusable component.
1# api/openapi.yaml (excerpt: cancel order endpoint)
2openapi: "3.0.3"
3info:
4 title: Santekno Shop — Order Service API
5 version: "1.0.0"
6 description: API for managing orders in Santekno Shop
7
8servers:
9 - url: https://api.santekno.com/v1
10 description: Production
11 - url: https://api-staging.santekno.com/v1
12 description: Staging
13 - url: http://localhost:8080/v1
14 description: Local development
15
16security:
17 - BearerAuth: []
18
19paths:
20 /orders/{id}:
21 delete:
22 operationId: cancelOrder
23 summary: Cancel an order
24 description: |
25 Cancels a PENDING order within the 15-minute cancellation window.
26 Restores stock for all items atomically.
27 Publishes ORDER_CANCELLED event to Kafka (best effort).
28 tags:
29 - orders
30 parameters:
31 - name: id
32 in: path
33 required: true
34 description: Order UUID
35 schema:
36 type: string
37 format: uuid
38 example: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
39 responses:
40 "204":
41 description: Order successfully cancelled
42 "401":
43 description: Authentication required
44 content:
45 application/json:
46 schema:
47 $ref: "#/components/schemas/ErrorResponse"
48 example:
49 error_code: UNAUTHORIZED
50 message: "authentication required"
51 "404":
52 description: Order not found
53 content:
54 application/json:
55 schema:
56 $ref: "#/components/schemas/ErrorResponse"
57 example:
58 error_code: ORDER_NOT_FOUND
59 message: "order not found"
60 "409":
61 description: Order cannot be cancelled
62 content:
63 application/json:
64 schema:
65 $ref: "#/components/schemas/OrderNotCancellableError"
66 examples:
67 wrongStatus:
68 summary: Order already confirmed
69 value:
70 error_code: ORDER_NOT_CANCELLABLE
71 message: "order cannot be cancelled: current status is CONFIRMED"
72 windowExpired:
73 summary: Cancellation window expired
74 value:
75 error_code: CANCEL_WINDOW_EXPIRED
76 message: "cancellation window has expired"
77 "500":
78 description: Internal server error
79 content:
80 application/json:
81 schema:
82 $ref: "#/components/schemas/ErrorResponse"
83 example:
84 error_code: INTERNAL_ERROR
85 message: "internal server error"
86
87components:
88 securitySchemes:
89 BearerAuth:
90 type: http
91 scheme: bearer
92 bearerFormat: JWT
93
94 schemas:
95 ErrorResponse:
96 type: object
97 required:
98 - error_code
99 - message
100 properties:
101 error_code:
102 type: string
103 description: Machine-readable error code
104 example: "ORDER_NOT_FOUND"
105 message:
106 type: string
107 description: Human-readable error message
108 example: "order not found"
109
110 OrderNotCancellableError:
111 allOf:
112 - $ref: "#/components/schemas/ErrorResponse"
113 properties:
114 current_status:
115 type: string
116 enum:
117 - CONFIRMED
118 - SHIPPED
119 - DELIVERED
120 - CANCELLED
121 description: Current order status
122 example: "CONFIRMED"Perhatikan bagaimana response 409 punya dua contoh berbeda (wrongStatus dan windowExpired) untuk satu status code — ini memetakan langsung ke AC9 dan AC10 di feature spec, membuktikan setiap keputusan bisnis punya jejak di contract.
08.5 Validasi OpenAPI Spec dengan Claude
Setelah spec di-generate, jangan langsung dipakai — minta Claude mereviewnya terhadap feature spec asli. Prompt berikut memastikan tidak ada AC yang tercecer sebelum kode di-generate.
1Review OpenAPI spec berikut dan identifikasi:
2
31. Apakah semua AC dari feature spec sudah ter-representasikan?
4 (sertakan feature spec untuk comparison)
52. Apakah response schema sudah lengkap dan consistent?
63. Apakah ada missing examples yang bisa membuat spec lebih berguna?
74. Apakah naming sudah konsisten dengan konvensi API yang ada?
85. Apakah ada security concern yang perlu ditambahkan?
96. Apakah spec ini bisa langsung dipakai untuk generate server code?
10
11Feature spec: @specs/order/cancel-order.md
12OpenAPI spec: @api/openapi.yamlDengan menyertakan feature spec sebagai pembanding, review ini menutup celah paling umum di contract-first: endpoint yang terlihat lengkap tapi diam-diam melewatkan satu error case yang ada di spec bisnis.
08.6 Menghasilkan Go Server Code dari OpenAPI
Begitu spec di-approve, kita bisa generate kode Go langsung dari OpenAPI menggunakan oapi-codegen. Perintah berikut memasang tool-nya.
1go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latestSetelah tool terpasang, kita perlu file konfigurasi yang menentukan apa saja yang akan di-generate. Konfigurasi berikut meminta types, server, dan spec sekaligus.
1# api/codegen-config.yaml
2package: api
3generate:
4 - types
5 - server
6 - spec
7output: internal/delivery/http/api/api.gen.goDengan konfigurasi tersimpan, generate kode cukup satu perintah yang menunjuk ke config dan file OpenAPI.
1oapi-codegen -config api/codegen-config.yaml api/openapi.yamlHasilnya adalah kode Go yang berisi tipe parameter dan interface server yang harus kamu implementasikan, seperti cuplikan berikut.
1// internal/delivery/http/api/api.gen.go (generated — jangan edit manual)
2package api
3
4// CancelOrderParams defines parameters for CancelOrder
5type CancelOrderParams struct {
6 // ID Order UUID
7 ID openapi_types.UUID `json:"id"`
8}
9
10// StrictServerInterface represents all server handlers
11type StrictServerInterface interface {
12 // CancelOrder cancels a pending order
13 // (DELETE /orders/{id})
14 CancelOrder(ctx context.Context, request CancelOrderRequestObject) (CancelOrderResponseObject, error)
15}Kunci dari output ini adalah StrictServerInterface: karena handler kamu wajib memenuhi interface ini, contract ditegakkan pada level compile-time — jika endpoint di spec berubah, kode yang tidak sinkron langsung gagal build.
08.7 Implementasi Handler dari Generated Interface
Setelah interface tergenerate, tugas kita adalah mengimplementasikannya dengan menghubungkan usecase dan memetakan error domain ke response OpenAPI. Handler berikut menunjukkan pemetaan lengkapnya.
1// internal/delivery/http/handler/order_handler.go
2package handler
3
4import (
5 "context"
6 "errors"
7 "log/slog"
8
9 "github.com/santekno/santekno-shop/internal/delivery/http/api"
10 "github.com/santekno/santekno-shop/internal/domain/order"
11 cancelorder "github.com/santekno/santekno-shop/internal/usecase/order/cancel_order"
12)
13
14type OrderHandler struct {
15 cancelOrderUC *cancelorder.CancelOrderUseCase
16}
17
18func NewOrderHandler(cancelOrderUC *cancelorder.CancelOrderUseCase) *OrderHandler {
19 return &OrderHandler{
20 cancelOrderUC: cancelOrderUC,
21 }
22}
23
24// CancelOrder implements api.StrictServerInterface
25// Implements specs/order/cancel-order.md v1.3
26func (h *OrderHandler) CancelOrder(
27 ctx context.Context,
28 request api.CancelOrderRequestObject,
29) (api.CancelOrderResponseObject, error) {
30
31 userID := ctx.Value(userIDKey{}).(uuid.UUID) // dari auth middleware
32
33 input := cancelorder.CancelOrderInput{
34 OrderID: uuid.UUID(request.Id),
35 UserID: userID,
36 }
37
38 err := h.cancelOrderUC.Execute(ctx, input)
39 if err != nil {
40 return h.mapCancelOrderError(err)
41 }
42
43 // AC7: 204 No Content
44 return api.CancelOrder204Response{}, nil
45}
46
47// mapCancelOrderError maps domain errors ke OpenAPI response types
48func (h *OrderHandler) mapCancelOrderError(err error) (api.CancelOrderResponseObject, error) {
49 var notCancellableErr *cancelorder.OrderNotCancellableError
50
51 switch {
52 // AC8: Order not found atau bukan milik user → 404
53 case errors.Is(err, cancelorder.ErrOrderNotFound):
54 return api.CancelOrder404JSONResponse{
55 ErrorCode: "ORDER_NOT_FOUND",
56 Message: "order not found",
57 }, nil
58
59 // AC9: Status bukan PENDING → 409
60 case errors.As(err, ¬CancellableErr):
61 return api.CancelOrder409JSONResponse{
62 ErrorCode: "ORDER_NOT_CANCELLABLE",
63 Message: fmt.Sprintf("order cannot be cancelled: current status is %s", notCancellableErr.CurrentStatus),
64 CurrentStatus: (*api.OrderNotCancellableErrorCurrentStatus)(¬CancellableErr.CurrentStatus),
65 }, nil
66
67 // AC10: Window expired → 409
68 case errors.Is(err, cancelorder.ErrCancelWindowExpired):
69 return api.CancelOrder409JSONResponse{
70 ErrorCode: "CANCEL_WINDOW_EXPIRED",
71 Message: "cancellation window has expired",
72 }, nil
73
74 // Unexpected error → 500
75 default:
76 slog.ErrorContext(ctx, "cancel order: unexpected error", "error", err)
77 return api.CancelOrder500JSONResponse{
78 ErrorCode: "INTERNAL_ERROR",
79 Message: "internal server error",
80 }, nil
81 }
82}Setiap cabang di mapCancelOrderError diberi komentar AC yang sesuai, dan tipe response seperti CancelOrder409JSONResponse berasal dari kode generated — artinya jika kamu salah mengembalikan tipe, compiler menolak. Inilah cara contract-first membuat drift antara spec dan implementasi mustahil lolos diam-diam.
08.8 Contract Testing dengan OpenAPI
Salah satu keuntungan terbesar contract-first adalah kemampuan menjalankan automated contract testing. Pendekatan pertama memakai schemathesis untuk memvalidasi server berjalan terhadap spec.
1# Install schemathesis
2pip install schemathesis
3
4# Jalankan contract test terhadap running server
5schemathesis run api/openapi.yaml \
6 --base-url http://localhost:8080/v1 \
7 --auth "Bearer $TEST_JWT_TOKEN" \
8 --endpoint "/orders/{id}" \
9 --method DELETEschemathesis otomatis menurunkan test case dari spec, jadi ia menangkap ketidaksesuaian response tanpa kamu menulis assertion manual. Pendekatan kedua adalah consumer-driven contract testing dengan Pact, yang memverifikasi kesepakatan antara producer dan consumer.
1// test/contract/order_cancel_pact_test.go
2package contract_test
3
4import (
5 "testing"
6 "github.com/pact-foundation/pact-go/v2/consumer"
7)
8
9func TestCancelOrderContract(t *testing.T) {
10 pact, err := consumer.NewV2Pact(consumer.MockHTTPProviderConfig{
11 Consumer: "NotificationService",
12 Provider: "OrderService",
13 })
14 if err != nil {
15 t.Fatal(err)
16 }
17
18 // Define the expected interaction
19 pact.
20 AddInteraction().
21 Given("order f47ac10b exists and is PENDING").
22 UponReceiving("a cancel order request").
23 WithRequest(consumer.Request{
24 Method: "DELETE",
25 Path: "/v1/orders/f47ac10b-58cc-4372-a567-0e02b2c3d479",
26 Headers: consumer.MapMatcher{
27 "Authorization": consumer.Like("Bearer eyJhbGci..."),
28 },
29 }).
30 WillRespondWith(consumer.Response{
31 Status: 204,
32 })
33
34 // Execute the test
35 err = pact.ExecuteTest(t, func(config consumer.MockServerConfig) error {
36 // Call our client
37 client := NewOrderClient(config.Host, config.Port)
38 return client.CancelOrder("f47ac10b-58cc-4372-a567-0e02b2c3d479")
39 })
40 if err != nil {
41 t.Fatal(err)
42 }
43}Dengan Pact, consumer (Notification Service) dan producer (Order Service) bisa diuji kesepakatannya secara independen tanpa integration test penuh — sangat berharga di arsitektur microservice di mana kedua sisi dikembangkan tim berbeda.
08.9 OpenAPI untuk Multiple Endpoints: Complete Create Order Spec
Cancel order hanya satu endpoint; API nyata punya banyak. Contoh berikut memperlihatkan spec yang lebih lengkap dengan create, get, dan cancel order sekaligus reusable components.
1# api/openapi.yaml — lebih lengkap
2paths:
3 /orders:
4 post:
5 operationId: createOrder
6 summary: Create a new order
7 tags: [orders]
8 requestBody:
9 required: true
10 content:
11 application/json:
12 schema:
13 $ref: "#/components/schemas/CreateOrderRequest"
14 example:
15 cart_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
16 shipping_address_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
17 responses:
18 "201":
19 description: Order created successfully
20 content:
21 application/json:
22 schema:
23 $ref: "#/components/schemas/CreateOrderResponse"
24 "400":
25 $ref: "#/components/responses/BadRequest"
26 "401":
27 $ref: "#/components/responses/Unauthorized"
28 "422":
29 description: Unprocessable entity
30 content:
31 application/json:
32 schema:
33 $ref: "#/components/schemas/ValidationError"
34
35 /orders/{id}:
36 get:
37 operationId: getOrder
38 summary: Get order by ID
39 tags: [orders]
40 parameters:
41 - $ref: "#/components/parameters/OrderID"
42 responses:
43 "200":
44 description: Order details
45 content:
46 application/json:
47 schema:
48 $ref: "#/components/schemas/OrderResponse"
49 "401":
50 $ref: "#/components/responses/Unauthorized"
51 "404":
52 $ref: "#/components/responses/NotFound"
53
54 delete:
55 operationId: cancelOrder
56 # ... (seperti di atas)
57
58components:
59 responses:
60 BadRequest:
61 description: Bad request
62 content:
63 application/json:
64 schema:
65 $ref: "#/components/schemas/ErrorResponse"
66
67 Unauthorized:
68 description: Authentication required
69 content:
70 application/json:
71 schema:
72 $ref: "#/components/schemas/ErrorResponse"
73 example:
74 error_code: UNAUTHORIZED
75 message: "authentication required"
76
77 NotFound:
78 description: Resource not found
79 content:
80 application/json:
81 schema:
82 $ref: "#/components/schemas/ErrorResponse"
83
84 parameters:
85 OrderID:
86 name: id
87 in: path
88 required: true
89 schema:
90 type: string
91 format: uuid
92 example: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
93
94 schemas:
95 CreateOrderRequest:
96 type: object
97 required: [cart_id, shipping_address_id]
98 properties:
99 cart_id:
100 type: string
101 format: uuid
102 description: ID of the cart to create order from
103 shipping_address_id:
104 type: string
105 format: uuid
106 description: ID of the shipping address
107
108 CreateOrderResponse:
109 type: object
110 required: [order_id, order_number, total_amount_cents, status]
111 properties:
112 order_id:
113 type: string
114 format: uuid
115 example: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
116 order_number:
117 type: string
118 example: "ORD-20250715-ABC1234"
119 total_amount_cents:
120 type: integer
121 format: int64
122 description: Total order amount in cents (IDR)
123 example: 1500000
124 status:
125 type: string
126 enum: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
127 example: PENDING
128 estimated_delivery:
129 type: string
130 format: date
131 description: Estimated delivery date (ISO 8601)
132 example: "2025-07-20"
133
134 OrderResponse:
135 type: object
136 required: [order_id, order_number, status, total_amount_cents, items, created_at]
137 properties:
138 order_id:
139 type: string
140 format: uuid
141 order_number:
142 type: string
143 status:
144 type: string
145 enum: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
146 total_amount_cents:
147 type: integer
148 format: int64
149 items:
150 type: array
151 items:
152 $ref: "#/components/schemas/OrderItem"
153 created_at:
154 type: string
155 format: date-time
156
157 OrderItem:
158 type: object
159 required: [product_id, product_name, quantity, price_cents]
160 properties:
161 product_id:
162 type: string
163 format: uuid
164 product_name:
165 type: string
166 example: "Laptop Gaming ASUS ROG"
167 quantity:
168 type: integer
169 minimum: 1
170 price_cents:
171 type: integer
172 format: int64
173 description: Price per unit in cents at time of order
174
175 ValidationError:
176 allOf:
177 - $ref: "#/components/schemas/ErrorResponse"
178 properties:
179 field:
180 type: string
181 description: Field that failed validation
182 example: "cart_id"Perhatikan pemakaian $ref yang agresif — OrderID, Unauthorized, dan ErrorResponse didefinisikan sekali lalu dipakai ulang di banyak endpoint. Pola ini menjaga konsistensi respons di seluruh API dan membuat perubahan cukup dilakukan di satu tempat.
08.10 Mengintegrasikan OpenAPI dengan Swagger UI
Untuk internal development, ada baiknya menyajikan spec lewat Swagger UI agar tim bisa mengeksplorasi API secara interaktif. Kode main.go berikut menyajikan spec dan UI-nya.
1// cmd/api/main.go
2package main
3
4import (
5 "github.com/labstack/echo/v4"
6 echoSwagger "github.com/swaggo/echo-swagger"
7)
8
9func main() {
10 e := echo.New()
11
12 // Serve OpenAPI spec
13 e.Static("/api-docs", "api")
14
15 // Swagger UI
16 e.GET("/swagger/*", echoSwagger.WrapHandler)
17
18 // API routes
19 api.RegisterHandlers(e, handler)
20
21 e.Start(":8080")
22}Dengan Swagger UI aktif, consumer team bisa mencoba endpoint langsung dari browser sebelum menulis satu baris kode integrasi — mempercepat feedback loop yang jadi inti dari paralel development.
08.11 Versioning OpenAPI Spec
API contract yang serius butuh strategi versioning yang jelas agar perubahan bisa dikelola tanpa mengejutkan consumer. Struktur direktori berikut memisahkan versi current, upcoming, dan deprecated.
1api/
2├── openapi.yaml ← current (v1)
3├── openapi-v2.yaml ← upcoming version (draft)
4└── deprecated/
5 └── openapi-v0.yaml ← deprecated, will be removed 2026-01-01Pemisahan direktori ini membuat status setiap versi terlihat sekilas. Untuk penomorannya, terapkan semantic versioning di dalam file spec seperti berikut.
1# Dalam openapi.yaml
2info:
3 version: "1.3.0"
4 # MAJOR: breaking change (field dihapus, endpoint path berubah)
5 # MINOR: backward compatible addition (field baru ditambah)
6 # PATCH: clarification, documentation update, no schema changeKonvensi MAJOR/MINOR/PATCH ini memberi consumer sinyal instan tentang risiko sebuah update. Agar penilaian breaking atau tidak tidak bergantung pada perasaan, mintalah Claude membandingkan dua versi dengan prompt berikut.
1Bandingkan dua versi OpenAPI spec berikut dan identifikasi:
21. Breaking changes: perubahan yang akan break existing consumers
3 (field dihapus, type berubah, endpoint path berubah)
42. Non-breaking additions: tambahan yang backward-compatible
53. Deprecations: field atau endpoint yang harus di-deprecated
6
7Versi lama: @api/openapi-v1.yaml
8Versi baru: @api/openapi-v2.yamlDengan analisis terstruktur ini, keputusan menaikkan MAJOR atau MINOR jadi objektif — kamu punya daftar konkret perubahan breaking, bukan sekadar dugaan.
08.12 OpenAPI untuk Microservice API Contract
Ketika dua service berkomunikasi, OpenAPI bisa berfungsi sebagai kontrak eksplisit di antara mereka. Struktur berikut memisahkan full spec dari subset yang dikonsumsi service lain.
1api/
2├── openapi.yaml ← full API spec
3└── contracts/
4 └── consumed-by-notification-service.yaml ← subset yang dipakai Notification ServiceMemisahkan contract per-consumer membuat batas dependency menjadi eksplisit. Isi dari file contract tersebut sebaiknya mencantumkan owner dan aturan koordinasi perubahan, seperti berikut.
1# api/contracts/consumed-by-notification-service.yaml
2# Contract antara Order Service (producer) dan Notification Service (consumer)
3# Created: 2025-07-15
4# Owner: @order-service-team
5# Consumer contact: @notification-service-team
6
7openapi: "3.0.3"
8info:
9 title: Order Service — Events consumed by Notification Service
10 description: |
11 This document describes the Kafka events published by Order Service
12 that are consumed by Notification Service.
13
14 Any changes to these events MUST be coordinated with @notification-service-team
15 and follow the breaking change process in ADR-008.Dengan mencantumkan owner dan referensi ke ADR-008 langsung di dalam contract, perubahan tak lagi bisa dilakukan sepihak — dokumentasi dependency menjadi mekanisme koordinasi, bukan sekadar catatan.
08.13 Prompting Claude untuk Review API Design
Selain memvalidasi kelengkapan, Claude bisa berperan sebagai API design reviewer yang menilai kualitas desain. Prompt berikut mengarahkannya menilai empat dimensi desain RESTful sekaligus.
1Kamu adalah senior API designer yang berpengalaman dengan RESTful API design
2dan OpenAPI specification.
3
4Review API design berikut dan berikan feedback tentang:
5
61. RESTful design principles:
7 - Apakah endpoint naming sudah sesuai resource-oriented design?
8 - Apakah HTTP methods digunakan dengan benar?
9 - Apakah HTTP status codes appropriate?
10
112. Schema design:
12 - Apakah response schema consistent di semua endpoint?
13 - Apakah ada redundant schema yang bisa di-reuse?
14 - Apakah naming convention consistent (camelCase vs snake_case)?
15
163. Security:
17 - Apakah semua endpoint yang perlu autentikasi sudah di-secure?
18 - Apakah ada potential information disclosure di error responses?
19
204. Developer experience:
21 - Apakah examples cukup untuk dipahami?
22 - Apakah descriptions jelas dan actionable?
23
24OpenAPI spec: @api/openapi.yamlReview desain semacam ini menangkap masalah yang tidak terlihat oleh validator otomatis — misalnya endpoint yang secara teknis valid tapi melanggar prinsip resource-oriented, atau error response yang tanpa sadar membocorkan informasi sensitif.
08.14 Automating OpenAPI Validation di CI/CD
Validasi contract sebaiknya berjalan otomatis di setiap perubahan spec, bukan mengandalkan kedisiplinan manual. Workflow GitHub Actions berikut memvalidasi spec dan mendeteksi breaking change pada setiap pull request.
1# .github/workflows/api-validation.yml
2name: API Contract Validation
3
4on:
5 pull_request:
6 paths:
7 - 'api/openapi.yaml'
8
9jobs:
10 validate-spec:
11 runs-on: ubuntu-latest
12 steps:
13 - uses: actions/checkout@v4
14
15 - name: Validate OpenAPI spec
16 uses: actions/setup-node@v4
17 with:
18 node-version: '20'
19
20 - run: npm install -g @apidevtools/swagger-parser
21
22 - name: Lint OpenAPI
23 run: swagger-parser validate api/openapi.yaml
24
25 - name: Check for breaking changes
26 uses: oasdiff/oasdiff-action@main
27 with:
28 base: refs/heads/main
29 revision: refs/heads/${{ github.head_ref }}
30
31 - name: Run contract tests
32 run: |
33 go test ./test/contract/... -vKarena workflow ini hanya terpicu saat api/openapi.yaml berubah, ia menjadi quality gate yang murah namun efektif: breaking change tertangkap di PR, jauh sebelum sampai ke consumer di production.
08.15 Menggunakan Claude untuk Generate Test Cases dari OpenAPI
OpenAPI spec juga bisa jadi sumber untuk menghasilkan test case yang komprehensif. Prompt berikut meminta Claude membuat test Go yang mencakup semua response code dan edge case dari spec.
1Berdasarkan OpenAPI spec untuk endpoint DELETE /orders/{id},
2generate test cases yang comprehensive dalam Go menggunakan
3testify/suite dan gomock.
4
5Test cases harus mencakup:
61. Semua response codes yang didefinisikan di spec (204, 401, 404, 409, 500)
72. Edge cases yang implied dari spec (concurrent requests, expired window)
83. Schema validation — response body harus match schema yang didefinisikan
9
10Gunakan pattern yang sama dengan: internal/usecase/order/create_order_test.goMenyuruh Claude mengikuti pattern dari test yang sudah ada memastikan test baru konsisten dengan konvensi proyek — sekaligus memanfaatkan spec sebagai daftar lengkap kasus yang wajib diuji, sehingga tidak ada response code yang terlewat.
08.16 OpenAPI sebagai Sumber untuk API Documentation
Satu OpenAPI spec bisa langsung diubah menjadi berbagai format dokumentasi tanpa menulis ulang. Perintah berikut menghasilkan HTML docs, Postman collection, dan contoh curl sekaligus.
1# Generate HTML docs dengan Redoc
2npx redoc-cli bundle api/openapi.yaml -o docs/api.html
3
4# Generate Postman collection
5npx openapi-to-postman \
6 --spec api/openapi.yaml \
7 --output docs/postman-collection.json
8
9# Generate dari Claude
10claude "Generate contoh curl commands untuk semua endpoints di api/openapi.yaml
11 Sertakan contoh request body dan expected response untuk masing-masing"Karena semua format ini diturunkan dari satu spec, dokumentasi otomatis selalu sinkron dengan contract — mengakhiri masalah klasik dokumentasi API yang basi begitu kode berubah.
08.17 Troubleshooting: Common Issues dengan OpenAPI di Go
Saat mengintegrasikan OpenAPI dengan Go, ada beberapa masalah yang berulang. Masalah pertama adalah tipe UUID yang tidak dikenali generator.
1# Solusi: gunakan format uuid
2properties:
3 id:
4 type: string
5 format: uuid ← ini yang penting, bukan hanya "type: string"Menambahkan format: uuid membuat generator menghasilkan tipe UUID yang benar alih-alih string mentah. Masalah kedua adalah overflow int64 saat JSON diparse oleh JavaScript.
1# Golang int64 bisa overflow jika JavaScript parse sebagai number
2# Solusi: gunakan x-go-type atau string untuk amount yang sangat besar
3properties:
4 amount_cents:
5 type: integer
6 format: int64
7 x-go-type: int64 ← explicit Go typeAnotasi x-go-type memaksa tipe Go yang eksplisit sehingga nilai besar tidak kehilangan presisi. Masalah ketiga adalah kebutuhan discriminator untuk response polymorphic.
1# Untuk berbagai tipe error response dengan schema berbeda
2components:
3 schemas:
4 OrderError:
5 oneOf:
6 - $ref: "#/components/schemas/OrderNotCancellableError"
7 - $ref: "#/components/schemas/CancelWindowExpiredError"
8 discriminator:
9 propertyName: error_code
10 mapping:
11 ORDER_NOT_CANCELLABLE: "#/components/schemas/OrderNotCancellableError"
12 CANCEL_WINDOW_EXPIRED: "#/components/schemas/CancelWindowExpiredError"Dengan discriminator yang memetakan error_code ke schema spesifik, tooling bisa memilih tipe yang tepat secara otomatis — memungkinkan satu field error menghasilkan beberapa bentuk response yang tetap type-safe.
08.18 Tips & Gotchas
💡 Tip 1: Mulai dengan spec minimal, tambahkan detail secara iteratif — spec OpenAPI yang terlalu lengkap di awal bisa jadi bottleneck. Mulai dengan endpoint, request/response schema, dan status codes, lalu tambahkan examples dan refinements secara iteratif.
💡 Tip 2: Gunakan $ref secara agresif untuk consistency — jika schema yang sama muncul di beberapa endpoint, selalu ekstrak ke components/schemas. Ini memastikan consistency dan membuat perubahan lebih mudah.
💡 Tip 3: Review spec bersama consumer team sebelum implementasi — tujuan contract-first adalah paralel development, dan ini hanya terjadi jika consumer team sudah review dan approve contract sebelum implementasi dimulai.
💡 Tip 4: Version spec di git dan track changes dengan commit messages yang jelas. Contoh commit message berikut menjelaskan sifat perubahan secara eksplisit.
1git commit -m "api: add CANCEL_WINDOW_EXPIRED response to DELETE /orders/{id}
2
3Adds new 409 response variant for when cancellation window is expired.
4This is a non-breaking addition (new error code, existing consumers
5not affected if they handle 409 generically).
6
7Related to: specs/order/cancel-order.md v1.3"Commit message yang menyebut eksplisit “non-breaking addition” dan mereferensikan spec membuat git history menjadi audit trail evolusi API — memudahkan menelusuri kapan dan kenapa sebuah response ditambahkan.
⚠️ Gotcha 1: Generated code tidak boleh diedit manual — file yang di-generate oapi-codegen harus ditandai // Code generated. DO NOT EDIT. dan tidak boleh diedit manual. Edit spec, generate ulang.
⚠️ Gotcha 2: OpenAPI spec tidak bisa express semua business rules — OpenAPI bagus untuk HTTP contract (input/output, status codes), tapi business rule kompleks seperti “cancel window 15 menit” tidak bisa di-express di OpenAPI — tetap perlu feature spec terpisah.
⚠️ Gotcha 3: Hindari circular references di schema — OpenAPI tidak support circular schema reference dengan baik di semua tools. Desain schema secara flat.
⚠️ Gotcha 4: Examples harus realistic, bukan placeholder — example: "string" atau example: 0 tidak berguna. Berikan contoh realistis seperti example: "ORD-20250715-ABC1234" atau example: 1500000.
08.19 Spec Governance: Siapa yang Approve OpenAPI Changes
Di proyek dengan banyak tim, perubahan OpenAPI butuh proses governance yang jelas agar tidak ada perubahan yang menabrak consumer tanpa persetujuan. Dokumen proses berikut memisahkan alur untuk perubahan non-breaking, breaking, dan deprecation.
1# API Contract Change Process
2
3## Non-breaking changes (tambah field, endpoint baru)
41. Developer buat PR dengan perubahan openapi.yaml
52. Review oleh: API Lead + Consumer Team Lead
63. Approval: 1 dari API Lead + 1 dari Consumer Team
74. Merge: bisa langsung setelah approval
8
9## Breaking changes (hapus field, ubah type, ubah path)
101. RFC (Request for Change) harus dibuat minimal 2 sprint sebelumnya
112. Review period: 1 minggu untuk semua consumer team
123. Deprecation notice: field/endpoint lama deprecated selama 1 sprint
134. Actual removal: setelah semua consumer sudah migrate
145. Approval: API Lead + semua Consumer Team Lead
15
16## Endpoint deprecation
17Tambahkan x-deprecated: true dan deadline di spec:
18deprecated: true
19x-deprecation-notice: "Will be removed in API v2 on 2026-01-01"Perbedaan perlakuan antara non-breaking dan breaking change di dokumen ini adalah inti governance-nya: perubahan berisiko rendah bisa cepat di-merge, sementara breaking change dipaksa melewati RFC dan deprecation period yang melindungi consumer.
08.20 Ringkasan
Contract-first API design menggunakan OpenAPI specification memberikan keuntungan konkret: paralel development, explicit breaking change detection, testable contract, dan AI code generation yang lebih akurat.
Alur kerja yang kita bangun: Feature spec → OpenAPI spec (via Claude) → Generated Go code → Contract tests → API documentation
Tools yang digunakan:
oapi-codegenuntuk generate Go interface dari OpenAPIschemathesisuntuk automated contract testingredoc-cliuntuk generate human-readable documentation
Governance yang penting:
- OpenAPI spec di git, versioned dengan semantic versioning
- Breaking changes butuh RFC dan deprecation period
- Consumer team harus review contract sebelum implementasi dimulai
Di artikel berikutnya, kita akan membahas dimensi spec yang sering terlewat: Spesifikasi Non-Fungsional — performance, security, scalability yang harus ada di spec sebelum implementasi.