M
Mr Sugiarto
Developer
18 Oct 2026 5 min read

Bagian 1 selesai dengan Notes API yang berdiri sendiri. Sekarang masuk Bagian 2: menerapkan workflow spec-first yang sama ke project yang strukturnya lebih dekat dengan kondisi kerja sehari-hari — boilerplate Go dengan hexagonal architecture (disebut juga ports & adapters).

Kenapa Hexagonal Architecture

Di hexagonal architecture, business logic (domain + use case) dipisah total dari detail teknis (HTTP framework, database, message broker) lewat port (interface) dan adapter (implementasi). HTTP handler adalah salah satu adapter — dan ini pas sekali dengan spec-first: ServerInterface hasil generate oapi-codegen adalah port di sisi inbound (HTTP), tinggal diimplementasikan oleh adapter handler.

Struktur Boilerplate

Project yang dipakai di bagian ini punya struktur seperti ini (ringkas):

internal/
├── domain/              # entity (User, dst) - murni business object
├── port/
│   ├── inbound/          # interface use case (dipanggil adapter HTTP/gRPC)
│   └── outbound/         # interface yang dibutuhkan use case (repository, cache)
├── app/                  # implementasi use case (service)
├── adapter/
│   ├── inbound/http/
│   │   ├── generated/    # hasil oapi-codegen, per file spec
│   │   ├── handler/      # implement ServerInterface
│   │   └── router/       # daftarkan semua handler ke Fiber
│   └── outbound/pgsql/   # implementasi repository pakai GORM + PostgreSQL
├── infra/db/migrations/  # migration SQL (goose)
└── bootstrap/            # dependency injection container

Sudah ada fitur User yang lengkap mengikuti pola ini. Tujuan Bagian 2 adalah menambahkan fitur Product dari nol, lapis demi lapis, mengikuti pola yang persis sama — sehingga di akhir, siapa pun yang sudah paham cara User API dibangun bisa langsung paham cara Product API dibangun, karena konvensinya identik.

Membedah Konvensi Spec yang Sudah Ada

Sebelum menulis product-api.yaml, kita baca dulu user-api.yaml yang sudah ada, supaya spec baru konsisten gaya penulisannya:

info:
  title: GoHexaClean User API
  description: |
    Production-ready User Management API built with Hexagonal + Clean Architecture.
  version: 1.0.0
  contact:
    name: API Support
    email: support@example.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: http://localhost:8080/api/v1
    description: Local development server

Beberapa konvensi yang diikuti: base URL sudah termasuk prefix /api/v1, response selalu dibungkus {success, message, data}, error pakai schema ErrorResponse yang konsisten, dan tiap schema request/response punya example di tiap field (bukan cuma type) — supaya dokumentasi yang dihasilkan langsung enak dibaca tanpa perlu nebak-nebak bentuk nilainya.

Menulis product-api.yaml

Mengikuti pola yang sama, berikut spec Product API — CRUD sederhana tanpa fitur tambahan (import/export sengaja tidak dimasukkan, supaya fokus pada mekanisme spec-first-nya, bukan kompleksitas fitur):

openapi: 3.0.3

info:
  title: GoHexaClean Product API
  description: |
    Product catalog API built with Hexagonal + Clean Architecture, added
    on top of the GoHexaClean boilerplate using a spec-first OpenAPI workflow.

    ## Features
    - Product CRUD operations
    - Pagination support
    - Generated server interface via oapi-codegen

  version: 1.0.0
  contact:
    name: API Support
    email: support@example.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: http://localhost:8080/api/v1
    description: Local development server

security: []

tags:
  - name: Products
    description: Product catalog CRUD endpoints

paths:
  /products:
    get:
      tags: [Products]
      summary: List products
      description: Get paginated list of products
      operationId: listProducts
      parameters:
        - name: page
          in: query
          description: Page number
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          description: Items per page
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 10
      responses:
        '200':
          description: List of products
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedProductResponse'

    post:
      tags: [Products]
      summary: Create product
      operationId: createProduct
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProductRequest'
      responses:
        '201':
          description: Product created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /products/{id}:
    get:
      tags: [Products]
      summary: Get product by ID
      operationId: getProductById
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Product found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        '404':
          description: Product not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    # put dan delete mengikuti pola yang sama persis dengan get,
    # bedanya di requestBody (untuk put) dan response (SuccessResponse untuk delete)

components:
  schemas:
    CreateProductRequest:
      type: object
      required: [name, price, stock]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
          example: Kaos IndoKoding
        price:
          type: number
          format: float
          minimum: 0
          example: 99000
        stock:
          type: integer
          minimum: 0
          example: 50

    Product:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          example: Kaos IndoKoding
        price:
          type: number
          format: float
          example: 99000
        stock:
          type: integer
          example: 50
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

(Schema UpdateProductRequest, ProductResponse, PaginatedProductResponse, SuccessResponse, ErrorResponse mengikuti pola yang sama dengan user-api.yaml — dibungkus {success, message, data} / {success, message, data, meta}.)

Validasi

Sebelum lanjut generate kode, spec ini dilint dulu — kebiasaan yang sudah dibangun di Part 5:

$ npx @redocly/cli lint api/openapi/product-api.yaml

api/openapi/product-api.yaml: validated in 25ms

Woohoo! Your API description is valid. 🎉
You have 2 warnings.

Dua warning yang tersisa sama sifatnya dengan Notes API: server localhost (memang development) dan satu operation tanpa response 4xx — keduanya informasional, bukan blocking.

Spec product-api.yaml ini kemudian ditaruh di folder yang sama dengan user-api.yaml (api/openapi/). Script scripts/generate-openapi.sh yang sudah ada di boilerplate otomatis mendeteksi semua file YAML di folder itu — artinya kita tidak perlu mengubah apa pun di script generate, cukup tambah file baru.

Di Part 7, spec ini di-generate jadi kode, dan kita bangun seluruh lapisan hexagonal untuk Product: domain, port, service, repository, sampai handler yang meng-implement interface hasil generate.

M
Mr Sugiarto

Developer

Bagian dari Series: OpenAPI Spec-First untuk Go Developer

Seri 8 bagian: belajar mendesain REST API dengan pendekatan spec-first di Go - menulis kontrak OpenAPI dulu, generate kode, lalu terapkan ke project h...

Lihat Series Lengkap