Skip to content
Santekno.com | Level Up Your Engineering Skills
EN
📖 0%
04 Nov 2026 · 23 min read ·Article 81 / 208
Go

Build E-commerce Golang from Scratch: From Business Needs to SDD Spec

Build Santekno Shop from scratch using Specification-Driven Development. From user stories to a structured spec file, ready to guide the implementation of the entire series.

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

From Business Needs to Specification: Santekno Shop from Scratch

Building an e-commerce Golang app from scratch with Specification-Driven Development starts not from code, but from business needs. Many e-commerce tutorials jump straight into code — “create a Product struct, create a handler, create a repository.” But here we do something different: we start from the business needs, derive them into a structured spec, and only then move to code. This is the flagship project of the entire AI-Driven Golang series.

Santekno Shop is not a demo project. It is an e-commerce application that will keep being extended in the upcoming topics (LLM features, RAG, AI Agents). By the end of the series, you will have one production-grade application that is fully AI-powered. Let’s start by understanding what we are building.

01.01 What is Santekno Shop?

Before writing a single line of spec, we need a full picture of the product we are going to build — the vision, the technical stack, the domain, and the target scale. The summary below becomes the reference for every design decision that follows.

text
 1Santekno Shop — B2C E-Commerce Platform Indonesia
 2
 3Vision: An AI-powered online shopping platform that
 4        gives a personal experience to every buyer.
 5
 6Technical stack:
 7  Backend:  Go 1.22, Echo v4
 8  Database: PostgreSQL 15 + pgvector (AI-ready)
 9  Cache:    Redis 7
10  Events:   Apache Kafka
11  Infra:    Docker + Kubernetes
12  Auth:     JWT + refresh token
13
14Domain:
15  - Product Catalog (products, categories, inventory)
16  - Order Management (cart, checkout, order lifecycle)
17  - User Management (auth, profile, address)
18  - Payment (payment gateway integration)
19  - Notification (email, push notification)
20
21Scale target:
22  100,000 MAU, 10,000 orders/day
23  P99 response time < 200ms
24  99.9% uptime

This picture tells us two important things: a mid-scale target (not hyperscale) and an AI-ready requirement from the very beginning (pgvector). These two constraints are exactly what will later steer our architecture decisions.

01.02 Why Start from the Business, Not the Code?

A fair question: why bother writing a spec when you could just start coding? The comparison of the anti-pattern versus the SDD pattern below shows the real cost of skipping the specification stage.

text
 1ANTI-PATTERN (what people often do):
 2  Developer: "We need a User, Product, Order entity."
 3  Immediately create tables, structs, and APIs.
 4
 5  Problems that surface in month 3:
 6  - "Oh, it turns out one product can have many variants"
 7  - "Hmm, cancelling an order actually has a 30-minute window"
 8  - "Wait, our payment needs to support installments"
 9  → Big refactoring, painful migrations
10
11SDD PATTERN (what we use):
12  PO + Developer: "Customer needs to be able to cancel a PENDING order"
13  Write the spec: complete Given/When/Then
14  Implement ACCORDING TO the spec
15
16  Benefit:
17  - No ambiguity at implementation time
18  - AI code review can validate spec compliance
19  - Test cases become obvious from the spec
20  - Breaking changes are detected when the spec changes

The point is that a spec moves requirement discovery from month 3 (expensive) to day one (cheap). Every ambiguity caught in the spec is a big refactoring that never has to happen later.

01.03 Domain Analysis: Stakeholders and Core Needs

Business needs always originate from stakeholders. The mapping below identifies the three main actors along with their core needs, and at the same time defines the focus for this topic.

text
 1Stakeholder 1: CUSTOMER (buyer)
 2  Core needs:
 3  - Easily find the product they are looking for
 4  - A fast and secure checkout process
 5  - Track order status in real time
 6  - Cancel an order if they change their mind
 7  - Receive a notification on every status change
 8
 9Stakeholder 2: SELLER/MERCHANT
10  Core needs:
11  - Upload and manage products easily
12  - See incoming orders that need processing
13  - Update stock in real time
14  - Clear sales reports
15
16Stakeholder 3: PLATFORM ADMIN
17  Core needs:
18  - Monitor platform health
19  - Moderate products and reviews
20  - Manage users and fraud detection
21
22For this topic, focus on: the CUSTOMER journey and the core order flow.
23Seller and Admin features are extended in later topics.

By limiting scope to the customer journey, we avoid over-engineering: the spec we write focuses on the flow that will actually be used first, rather than every possibility at once.

01.04 Event Storming: Finding Domain Events

Once we know the needs, we dig into what events happen in the system. The result of the lightweight event storming below becomes the raw material for determining aggregates and bounded contexts.

text
 1Lightweight Event Storming process for Santekno Shop:
 2
 3Identified Domain Events:
 4  [ORANGE = domain events]
 5
 6  UserRegistered
 7  UserLoggedIn
 8  ProductCreated
 9  ProductStockUpdated
10  CartItemAdded
11  CartItemRemoved
12  OrderPlaced
13  OrderConfirmed
14  OrderShipped
15  OrderDelivered
16  OrderCancelled
17  PaymentInitiated
18  PaymentCompleted
19  PaymentFailed
20  NotificationSent
21
22Commands that trigger events:
23  RegisterUser → UserRegistered
24  PlaceOrder → OrderPlaced → PaymentInitiated
25  ConfirmPayment → PaymentCompleted → OrderConfirmed
26  CancelOrder → OrderCancelled → (notify customer)
27  ShipOrder → OrderShipped → (notify customer)
28
29Identified Aggregates:
30  User (UserID)
31  Product (ProductID)
32  Cart (UserID — 1 cart per user)
33  Order (OrderID)
34  Payment (PaymentID)

These events and aggregates are our “domain vocabulary.” All specs, tables, and Kafka topics will later consistently use these names — a single source of truth for naming.

01.05 Bounded Contexts for Santekno Shop

From the list of events and aggregates, we group responsibilities into isolated bounded contexts. The mapping below defines module boundaries and their integration patterns.

text
 1Bounded Context 1: USER MANAGEMENT
 2  Entities: User, Address, Session
 3  Responsibilities: auth, profile, address book
 4  Events consumed: -
 5  Events produced: UserRegistered, UserLoggedIn
 6
 7Bounded Context 2: PRODUCT CATALOG
 8  Entities: Product, Category, Inventory
 9  Responsibilities: catalog, search, stock management
10  Events consumed: OrderPlaced (to reserve stock)
11  Events produced: ProductCreated, StockUpdated, StockReserved
12
13Bounded Context 3: ORDER MANAGEMENT
14  Entities: Cart, Order, OrderItem
15  Responsibilities: cart, checkout, order lifecycle
16  Events consumed: PaymentCompleted, PaymentFailed
17  Events produced: OrderPlaced, OrderConfirmed, OrderCancelled, OrderShipped
18
19Bounded Context 4: PAYMENT
20  Entities: Payment, Transaction
21  Responsibilities: gateway integration, reconciliation
22  Events consumed: OrderPlaced
23  Events produced: PaymentInitiated, PaymentCompleted, PaymentFailed
24
25Bounded Context 5: NOTIFICATION
26  Entities: NotificationLog, Template
27  Responsibilities: email, push notification
28  Events consumed: all events that need a notification
29  Events produced: NotificationSent
30
31For this topic: implement BC 1, 3 (core), 4 (simplified).
32BC 2 and 5 are built in parallel and integrated.

These clear boundaries between contexts are what keep our modular monolith tidy — each context can later be extracted into a microservice without tearing apart the domain logic.

01.06 The Spec Format We Use

So that specs are consistent and machine-validatable, we use a Gherkin-inspired Given/When/Then template. The skeleton below is the mold used for every feature.

markdown
 1# Santekno Shop Spec Format
 2# Uses Gherkin-inspired Given/When/Then
 3
 4## Feature: [Feature Name]
 5
 6### Context
 7[Business context paragraph — why this feature exists]
 8
 9### Actors
10- [Actor 1]: [role and capabilities]
11- [Actor 2]: [role and capabilities]
12
13### Acceptance Criteria
14
15**AC[N]: [Scenario Name]**
16- Given: [precondition]
17- When: [action / HTTP method + endpoint]
18- Then: [expected HTTP response code]
19- Then: [expected response body]
20- Then: [expected side effect — DB, event, etc.]
21- Constraints: [business rules, limits, timing]
22
23### Business Rules
24- [Rule 1]
25- [Rule 2]
26
27### Data Contracts
28Request:
29{...}
30Response:
31{...}
32
33### Implementation Notes
34- [Relevant technical note]
35
36### Links
37- Handler: `delivery/http/handler/[file].go`
38- UseCase: `internal/usecase/[domain]/[file].go`
39- Repository: `internal/repository/postgres/[file].go`

This structured template matters: every AC has an explicit Given/When/Then, so it can be mapped directly to a test case and validated by CI. Next, we fill this template out for seven core features.

01.07 Spec 1: User Registration

The first and most fundamental feature is user registration. The spec below defines four scenarios — from the happy path to every possible error case.

markdown
 1## Feature: User Registration
 2
 3### Context
 4A new customer needs to register an account before making a purchase.
 5Email is used as the primary identifier. Passwords are hashed using bcrypt.
 6
 7### Actors
 8- Guest User: does not yet have an account
 9
10### Acceptance Criteria
11
12**AC1: Registration succeeds**
13- Given: email is not yet registered in the system
14- Given: password meets the requirements (min 8 characters, contains a digit)
15- When: POST /api/v1/auth/register
16  {"email": "user@example.com", "password": "Secret123", "name": "John Doe"}
17- Then: 201 Created
18- Then: Response body contains: {"id": "uuid", "email": "...", "name": "..."}
19- Then: A token is NOT returned on registration (user needs to log in separately)
20- Then: Event `UserRegistered` is published to Kafka
21
22**AC2: Email already registered**
23- Given: the email already exists in the system
24- When: POST /api/v1/auth/register
25- Then: 409 Conflict
26- Then: {"error": "EMAIL_ALREADY_REGISTERED"}
27
28**AC3: Password does not meet requirements**
29- Given: password is shorter than 8 characters OR has no digit
30- When: POST /api/v1/auth/register
31- Then: 400 Bad Request
32- Then: {"error": "WEAK_PASSWORD", "detail": "..."}
33
34**AC4: Invalid email format**
35- When: POST /api/v1/auth/register with email "not-an-email"
36- Then: 400 Bad Request
37- Then: {"error": "INVALID_EMAIL"}
38
39### Business Rules
40- Email must be lowercased before being stored
41- Password is hashed with bcrypt cost 12
42- Name: 2-100 characters
43- No rate limit on registration (but there is one on login)
44
45### Implementation Notes
46- Repository returns nil, nil if the email is not found
47- Do not reveal whether an email is already registered in the production error message
48  (to prevent email enumeration attacks) — but for dev, it's OK

Note that the error cases (AC2-AC4) are defined in the same detail as the happy path. This is the power of a spec: every possible error already has a definite code and HTTP status, long before implementation.

01.08 Spec 2: User Login

After registration, a user needs to be able to log in and receive a JWT. The login spec below covers authentication, credential security, and rate limiting.

markdown
 1## Feature: User Login (JWT Authentication)
 2
 3### Context
 4A registered user needs to be able to log in and get a JWT token
 5to access endpoints that require authentication.
 6
 7### Acceptance Criteria
 8
 9**AC1: Login succeeds**
10- Given: valid email and password, active user
11- When: POST /api/v1/auth/login
12  {"email": "user@example.com", "password": "Secret123"}
13- Then: 200 OK
14- Then:
15  {
16    "access_token": "eyJ...",
17    "refresh_token": "...",
18    "expires_in": 3600,
19    "token_type": "Bearer"
20  }
21- Then: access_token valid for 1 hour
22- Then: refresh_token valid for 7 days, stored in the DB
23
24**AC2: Email not found**
25- When: POST /api/v1/auth/login with an email that does not exist
26- Then: 401 Unauthorized
27- Then: {"error": "INVALID_CREDENTIALS"}
28- Note: DO NOT distinguish "email not found" from "wrong password"
29
30**AC3: Wrong password**
31- When: POST /api/v1/auth/login with the wrong password
32- Then: 401 Unauthorized
33- Then: {"error": "INVALID_CREDENTIALS"}
34
35**AC4: Rate limiting — too many attempts**
36- Given: 5 failed logins within 15 minutes from the same IP
37- When: the 6th attempt
38- Then: 429 Too Many Requests
39- Then: {"error": "TOO_MANY_ATTEMPTS", "retry_after": 900}
40
41### Business Rules
42- Access token: HS256, expires in 1 hour
43- Refresh token: random 32 bytes, expires in 7 days
44- Rate limit: 5 attempts per 15 minutes per IP (in Redis)
45- No difference in response between email-not-found and wrong-password
46
47### Data Contracts
48Request: {"email": string, "password": string}
49Response: {"access_token": string, "refresh_token": string, "expires_in": int}

Security details such as “don’t distinguish the email vs password error” (to prevent enumeration) and the rate limit are stated explicitly in the spec. Security is designed in, not bolted on later.

01.09 Spec 3: Place Order (Core Flow)

This is the heart of e-commerce: creating an order. The spec below governs order creation with atomic stock reservation and payment initiation.

markdown
 1## Feature: Place Order
 2
 3### Context
 4A logged-in customer can place an order. The order is created
 5with PENDING status, then the system starts the payment process.
 6Stock is reserved when the order is created and returned if payment fails.
 7
 8### Acceptance Criteria
 9
10**AC1: Order created successfully**
11- Given: user is logged in (valid JWT)
12- Given: all product_ids are valid and stock is sufficient
13- Given: payment_method is valid (BANK_TRANSFER | QRIS | CREDIT_CARD)
14- When: POST /api/v1/orders
15  {
16    "items": [
17      {"product_id": "uuid", "quantity": 2}
18    ],
19    "shipping_address_id": "uuid",
20    "payment_method": "QRIS"
21  }
22- Then: 201 Created
23- Then: Response includes order_id, status: "PENDING", total_amount_idr, payment_url
24- Then: Stock is reserved (deducted from available, moved to reserved)
25- Then: Event `OrderPlaced` is published to Kafka
26- Then: Payment initiated (payment_url available)
27
28**AC2: One of the products is out of stock**
29- Given: the requested quantity exceeds available stock for one of the products
30- When: POST /api/v1/orders
31- Then: 422 Unprocessable Entity
32- Then: {"error": "INSUFFICIENT_STOCK", "product_id": "uuid"}
33- Then: Stock does NOT change (atomic check)
34
35**AC3: Product not found**
36- Given: one of the product_ids does not exist
37- When: POST /api/v1/orders
38- Then: 404 Not Found
39- Then: {"error": "PRODUCT_NOT_FOUND", "product_id": "uuid"}
40
41**AC4: User not logged in**
42- When: POST /api/v1/orders without a JWT header
43- Then: 401 Unauthorized
44- Then: {"error": "UNAUTHORIZED"}
45
46**AC5: Empty cart**
47- When: POST /api/v1/orders with items: []
48- Then: 400 Bad Request
49- Then: {"error": "EMPTY_ORDER"}
50
51### Business Rules
52- Order amount in IDR as int64 (cents: 1 IDR = 100 cents)
53  → Rp 50,000 = 5_000_000 cents
54- Stock reservation and order creation must be atomic (one DB transaction)
55- Payment URL expires in 24 hours
56- Maximum 10 items per order
57- Maximum quantity per item: 100
58
59### Data Contracts
60total_amount_idr: int64 (cents, not rupiah directly)

The business rules “atomic stock reservation” and “int64 cents” here are not trivial details — both prevent two classic e-commerce bugs: overselling during a race condition and money rounding. The spec enforces them before any code exists.

01.10 Spec 4: Cancel Order

Customers sometimes change their minds. The cancel order spec below sets the time window, stock restore, and every condition under which cancellation is rejected.

markdown
 1## Feature: Cancel Order
 2
 3### Context
 4A customer can cancel an order that is still PENDING within a 30-minute window.
 5After 30 minutes, the order cannot be cancelled because it has entered processing.
 6
 7### Acceptance Criteria
 8
 9**AC1: Cancel a PENDING order within the window**
10- Given: order status = PENDING
11- Given: order was created < 30 minutes ago
12- Given: request comes from the user who owns this order
13- When: DELETE /api/v1/orders/{order_id}
14- Then: 204 No Content
15- Then: Order status changes to CANCELLED
16- Then: Stock is restored (reserved → available)
17- Then: Event `OrderCancelled` is published to Kafka
18- Note: Stock restore and status update must be atomic (one transaction)
19
20**AC2: Order is no longer PENDING**
21- Given: order status = CONFIRMED, SHIPPED, or DELIVERED
22- When: DELETE /api/v1/orders/{order_id}
23- Then: 409 Conflict
24- Then: {"error": "ORDER_NOT_CANCELLABLE"}
25
26**AC3: Cancel window has passed**
27- Given: order status = PENDING
28- Given: order was created > 30 minutes ago
29- When: DELETE /api/v1/orders/{order_id}
30- Then: 409 Conflict
31- Then: {"error": "CANCEL_WINDOW_EXPIRED"}
32
33**AC4: Order not found / not owned by user**
34- When: DELETE /api/v1/orders/{order_id} for an order that does not exist
35  or belongs to another user
36- Then: 404 Not Found
37- Then: {"error": "ORDER_NOT_FOUND"}
38
39### Business Rules
40- Cancel window: 30 minutes from Order.CreatedAt
41- Stock restore in the same transaction as the status update
42- Kafka event publishing may be async (does not block the response)
43- After cancellation, the payment URL becomes invalid

Two different conflict statuses (ORDER_NOT_CANCELLABLE vs CANCEL_WINDOW_EXPIRED) let the frontend give the user the right message. A spec that distinguishes the two prevents a confusing UX.

01.11 Spec 5: Get Order List

A user needs to see their order history. The spec below governs pagination, status filtering, and data isolation between users.

markdown
 1## Feature: Get Order List
 2
 3### Context
 4A customer can view a list of their orders, with pagination and status filtering.
 5
 6### Acceptance Criteria
 7
 8**AC1: List orders with pagination**
 9- Given: user is logged in
10- When: GET /api/v1/orders?page=1&limit=10
11- Then: 200 OK
12- Then:
13  {
14    "data": [{...order objects...}],
15    "pagination": {
16      "page": 1,
17      "limit": 10,
18      "total": 25,
19      "total_pages": 3
20    }
21  }
22
23**AC2: Filter by status**
24- When: GET /api/v1/orders?status=PENDING
25- Then: only return orders with PENDING status
26
27**AC3: Default pagination**
28- When: GET /api/v1/orders (without query params)
29- Then: page=1, limit=10 as defaults
30
31**AC4: A user only sees their own orders**
32- User A cannot see User B's orders
33- Filtering by user_id is done in the query, not in the application layer
34
35### Business Rules
36- Max limit per page: 100
37- Default limit: 10
38- Orders sorted by created_at DESC (newest first)
39- Only return orders belonging to the authenticated user

Note AC4: filtering by user_id is done at the query level, not in the application. This is a security decision the spec enforces — data leakage between users becomes impossible, not merely “attempted to avoid.”

01.12 Spec 6: Product Catalog

Without a product catalog, there is nothing to buy. The spec below defines listing, search, and product detail along with their business rules.

markdown
 1## Feature: Product Catalog
 2
 3### Context
 4A user can browse products, search, and view product detail.
 5Product listing supports filter and sort.
 6
 7### Acceptance Criteria
 8
 9**AC1: List products with filters**
10- When: GET /api/v1/products?category=electronics&min_price=100000&max_price=500000
11- Then: 200 OK with a list of products matching the filter
12- Then: Each product includes: id, name, price_idr, stock, category, images[0]
13
14**AC2: Search products**
15- When: GET /api/v1/products?q=running+shoes
16- Then: 200 OK with relevant products
17- Then: Sorted by relevance score
18
19**AC3: Product detail**
20- When: GET /api/v1/products/{product_id}
21- Then: 200 OK with full detail:
22  id, name, description, price_idr, stock, category, images[], specs{}
23- Given: product does not exist
24- Then: 404 Not Found
25
26**AC4: Product out of stock**
27- When: GET /api/v1/products/{product_id}
28- Then: 200 OK, stock = 0
29- Note: An out-of-stock product is still returned, not hidden
30
31### Business Rules
32- Price in IDR as int64 cents
33- Images: array of URLs, max 10 images
34- Search: full-text search using PostgreSQL tsvector
35  (will be upgraded to pgvector semantic search in a later topic)
36- Default sort: created_at DESC
37- Max 50 items per page

The search business rule that uses tsvector now, but is prepared to upgrade to pgvector, is an example of a spec that thinks about evolution. The AI-ready foundation is planted from the most basic feature.

01.13 Spec 7: Payment Webhook

The final feature connects us to the outside world: the payment gateway. The webhook spec below governs signature verification, order status updates, and idempotency.

markdown
 1## Feature: Payment Webhook Handler
 2
 3### Context
 4The payment gateway (simulated) will send a webhook when a payment
 5succeeds or fails. The system needs to handle this webhook and update
 6the order status accordingly.
 7
 8### Acceptance Criteria
 9
10**AC1: Payment succeeds**
11- When: POST /api/v1/webhooks/payment
12  {
13    "order_id": "uuid",
14    "payment_id": "pay_xxx",
15    "status": "PAID",
16    "amount_idr": 5000000,
17    "signature": "hmac-sha256-hash"
18  }
19- Given: signature is valid (HMAC-SHA256 with a shared secret)
20- Then: 200 OK
21- Then: Order status changes to CONFIRMED
22- Then: Payment record updated with payment_id
23- Then: Event `PaymentCompleted` is published to Kafka
24- Then: Event `OrderConfirmed` is published to Kafka
25
26**AC2: Payment fails**
27- When: POST /api/v1/webhooks/payment with status: "FAILED"
28- Then: 200 OK
29- Then: Order status changes to CANCELLED
30- Then: Stock is restored
31- Then: Event `PaymentFailed` is published to Kafka
32
33**AC3: Invalid signature**
34- When: POST /api/v1/webhooks/payment with a wrong signature
35- Then: 401 Unauthorized
36- Then: {"error": "INVALID_SIGNATURE"}
37- Note: Log the attempt for security monitoring
38
39**AC4: Idempotency — duplicate webhook**
40- Given: a webhook for the same payment_id was already processed
41- When: the same webhook is received again
42- Then: 200 OK (idempotent)
43- Then: No state change
44
45### Business Rules
46- Signature: HMAC-SHA256(payload_json, WEBHOOK_SECRET)
47- Idempotency key: payment_id
48- Timeout for processing a webhook: 10 seconds
49- Store a webhook log for audit

AC4 (idempotency) is crucial because payment gateways often send the same webhook twice. With payment_id as the idempotency key in the spec, a duplicate will never double-confirm an order.

01.14 Architecture Decision Records (ADR)

Some cross-feature technical decisions need to be recorded formally so they stay consistent. The five ADRs below become the unwritten law that binds the entire implementation.

text
 1ADR 001: Monetary Values use int64 cents
 2  Status: ACCEPTED
 3  Context: Floating point arithmetic is not safe for financial data
 4  Decision: All monetary values are stored as int64 (IDR cents)
 5            1 IDR = 100 cents → Rp 50,000 = 5,000,000 cents
 6  Consequences: All display/formatting is done client side
 7
 8ADR 002: Repository Not-Found uses nil, nil
 9  Status: ACCEPTED
10  Context: Distinguish "not found" from infrastructure errors
11  Decision: Repository returns (nil, nil) for not-found
12            Usecase interprets a nil result as "not found"
13  Consequences: A nil check is needed in every usecase after a repo call
14
15ADR 003: Stock Management uses optimistic locking
16  Status: ACCEPTED
17  Context: Race condition on stock deduction
18  Decision: Use UPDATE ... WHERE stock >= quantity RETURNING *
19            If 0 rows affected = insufficient stock
20  Consequences: No need to SELECT then UPDATE (race-free)
21
22ADR 004: Event publishing via Kafka Outbox Pattern
23  Status: ACCEPTED
24  Context: Ensure events are published if the DB transaction succeeds
25  Decision: Store the event in an outbox table in the same transaction
26            A background worker polls the outbox and publishes to Kafka
27  Consequences: At-least-once delivery; consumers must be idempotent
28
29ADR 005: pgvector for future AI features
30  Status: ACCEPTED
31  Context: A later topic will need semantic search
32  Decision: Install the pgvector extension from the start
33            The products table has an embedding VECTOR(1536) column
34  Consequences: Slight overhead now, zero migration cost later

ADRs make decisions traceable: six months from now, when someone asks “why is money an int64?”, the answer is already recorded complete with its context and consequences.

01.15 Error Code Convention

So that clients can handle errors consistently, we define a centralized error code registry. The Go code below is the single source of truth for every error code.

go
 1// internal/domain/errors.go
 2// Standard error codes for Santekno Shop
 3
 4package domain
 5
 6// Auth errors
 7const (
 8    ErrCodeEmailAlreadyRegistered = "EMAIL_ALREADY_REGISTERED"
 9    ErrCodeWeakPassword           = "WEAK_PASSWORD"
10    ErrCodeInvalidEmail           = "INVALID_EMAIL"
11    ErrCodeInvalidCredentials     = "INVALID_CREDENTIALS"
12    ErrCodeTooManyAttempts        = "TOO_MANY_ATTEMPTS"
13    ErrCodeUnauthorized           = "UNAUTHORIZED"
14    ErrCodeForbidden              = "FORBIDDEN"
15)
16
17// Order errors
18const (
19    ErrCodeOrderNotFound       = "ORDER_NOT_FOUND"
20    ErrCodeOrderNotCancellable = "ORDER_NOT_CANCELLABLE"
21    ErrCodeCancelWindowExpired = "CANCEL_WINDOW_EXPIRED"
22    ErrCodeInsufficientStock   = "INSUFFICIENT_STOCK"
23    ErrCodeProductNotFound     = "PRODUCT_NOT_FOUND"
24    ErrCodeEmptyOrder          = "EMPTY_ORDER"
25)
26
27// Payment errors
28const (
29    ErrCodeInvalidSignature = "INVALID_SIGNATURE"
30    ErrCodePaymentNotFound  = "PAYMENT_NOT_FOUND"
31)
32
33// Generic
34const (
35    ErrCodeInternalError    = "INTERNAL_ERROR"
36    ErrCodeValidationFailed = "VALIDATION_FAILED"
37    ErrCodeNotFound         = "NOT_FOUND"
38)
39
40// HTTP error response format
41type ErrorResponse struct {
42    Error  string `json:"error"`
43    Detail string `json:"detail,omitempty"`
44}

With every error code in a single file, there is no more typo of “order_not_found” vs “ORDER_NOT_FOUND”. The frontend can even generate an error enum directly from these constants.

01.16 API Design Principles

All endpoints must behave consistently. The API design conventions below become the style contract that applies across the whole service.

text
 1Santekno Shop API Design Conventions:
 2
 3BASE URL: /api/v1/
 4
 5AUTHENTICATION:
 6  Header: Authorization: Bearer {access_token}
 7  Endpoints that do not need auth: POST /auth/register, POST /auth/login,
 8                                   GET /products, GET /products/:id
 9
10RESPONSE FORMAT:
11  Success (list):   {"data": [...], "pagination": {...}}
12  Success (single): {object fields directly, not wrapped}
13  Error: {"error": "ERROR_CODE", "detail": "..."}
14
15HTTP STATUS CODES:
16  200: OK (successful GET)
17  201: Created (successful POST that creates a resource)
18  204: No Content (successful DELETE)
19  400: Bad Request (input validation failed)
20  401: Unauthorized (no / invalid token)
21  403: Forbidden (valid token but no access)
22  404: Not Found (resource does not exist)
23  409: Conflict (state conflict — order cannot be cancelled)
24  422: Unprocessable Entity (business logic fail — out of stock)
25  429: Too Many Requests (rate limit)
26  500: Internal Server Error
27
28NAMING:
29  Endpoint: snake_case, plural noun
30  Query params: snake_case
31  Response body: snake_case
32
33MONETARY VALUES:
34  All in integer cents (IDR)
35  Field name suffix: _idr (e.g., total_amount_idr, price_idr)
36
37PAGINATION:
38  Query: ?page=1&limit=10
39  Response: {"data": [...], "pagination": {"page": 1, "limit": 10, "total": 100}}

These conventions remove “per-endpoint surprises.” Once a developer understands the pattern, they can predict the response shape of any endpoint without reading the documentation every time.

01.17 Spec Validation with CI

A good spec is useless if it is not enforced. The CI configuration below runs an automatic spec-compliance audit on every change.

yaml
 1# Every spec created will be validated by CI
 2# using the specify audit from the previous topic
 3
 4# .specify/config.yml
 5version: '1.0'
 6project:
 7  name: Santekno Shop
 8  language: go
 9  framework: echo
10
11audit:
12  threshold: 80
13  scope:
14    include:
15      - 'internal/usecase/**'
16      - 'delivery/http/handler/**'
17
18# Spec files we have already created in this article:
19# .specify/features/user-registration/spec.md
20# .specify/features/user-login/spec.md
21# .specify/features/place-order/spec.md
22# .specify/features/cancel-order/spec.md
23# .specify/features/get-orders/spec.md
24# .specify/features/product-catalog/spec.md
25# .specify/features/payment-webhook/spec.md

With a threshold of 80, any PR that drops spec compliance below the bar is automatically rejected. The spec turns from a passive document into an active quality gate.

01.18 Sprint Planning from the Spec

The spec also becomes the work plan directly. The mapping below shows how the seven specs are translated into four two-week sprints.

text
 1Sprint 1 (2 weeks): Auth + User Foundation
 2  Spec: user-registration, user-login
 3  Deliverable:
 4    - Database schema: users, sessions, refresh_tokens
 5    - Handler: POST /auth/register, POST /auth/login, POST /auth/refresh
 6    - UseCase: RegisterUser, LoginUser, RefreshToken
 7    - Middleware: JWT authentication
 8    - Tests: all ACs from the spec
 9
10Sprint 2 (2 weeks): Product Catalog
11  Spec: product-catalog
12  Deliverable:
13    - Database schema: products, categories, product_images
14    - Handler: GET /products, GET /products/:id
15    - UseCase: ListProducts, GetProduct, SearchProducts
16    - Full-text search with tsvector
17    - Tests: all ACs from the spec
18
19Sprint 3 (2 weeks): Order Management
20  Spec: place-order, cancel-order, get-orders
21  Deliverable:
22    - Database schema: orders, order_items, inventory_reservations
23    - Handler: POST /orders, DELETE /orders/:id, GET /orders
24    - UseCase: PlaceOrder, CancelOrder, GetOrders
25    - Kafka event publishing
26    - Tests: all ACs from the spec
27
28Sprint 4 (2 weeks): Payment + Integration
29  Spec: payment-webhook
30  Deliverable:
31    - Database schema: payments, webhook_logs, outbox
32    - Handler: POST /webhooks/payment
33    - UseCase: HandlePaymentWebhook
34    - Outbox pattern implementation
35    - E2E test: full order flow

Because each deliverable is derived directly from the spec’s ACs, sprint estimates become far more accurate — there are no “hidden requirements” surfacing mid-sprint.

01.19 Tools Used

Before the first sprint, the working environment must be ready. The commands below install every tool needed throughout this topic and verify them at once.

bash
 1# Install all the tools we need
 2
 3# Go (already installed)
 4go version  # v1.22+
 5
 6# Database
 7docker pull postgres:15  # with pgvector
 8
 9# specify CLI (from the previous topic)
10npm install -g @santekno/spec-kit
11
12# Database migration
13go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
14
15# Mock generation
16go install go.uber.org/mock/mockgen@latest
17
18# API testing
19brew install httpie  # or: curl + jq
20
21# Development tools
22go install github.com/air-verse/air@latest  # hot reload
23go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
24
25# Verify everything is ready:
26go version && migrate -version && mockgen --version
27echo "All tools ready"

Running the verification block at the end ensures no tool is missing when implementation begins — better to fail here than mid coding session.

01.20 Summary: A Solid Foundation Before the First Line of Code

The specs we have written in this article are the contract between business and engineering. Every line of code in the following articles will be derived from these specs.

7 specs already defined:

  • User Registration (4 ACs)
  • User Login (4 ACs)
  • Place Order (5 ACs)
  • Cancel Order (4 ACs)
  • Get Order List (4 ACs)
  • Product Catalog (4 ACs)
  • Payment Webhook (4 ACs)

Total: 29 Acceptance Criteria that will become test cases.

With these specs, the CI pipeline from the previous topic can immediately enforce compliance — every PR that touches the implementation will be audited against these specs. The spec is the truth; the code is its implementation.

In the next article, we design the multi-service architecture of Santekno Shop with the help of Claude — from the monolith vs microservice decision to the complete dependency graph.

Related Articles

💬 Comments