404 Not Found

404 Not Found


nginx

Kotlin Project Design Explained

All 24 lessons converge here — Charlie starts from requirements analysis and designs the OrderProcessor's domain model, layered architecture, tech stack selection, and API contracts. This is step one of the real-world project.

1. What You'll Learn


2. An Architect's Real Story

(1) Pain Point: Designing Without a Plan Guarantees Rework

Charlie's team once skipped design and started coding directly. Three months later, the domain model didn't match business requirements — 60% of the code had to be rewritten. Loss: 3 months of development + 2 months of rework.

(2) Design-First Approach

TEXT
Week 1: Requirements → Domain Model → State Machine
Week 2: Architecture → Tech Stack → API Contract
Week 3: Database Schema → Module Boundaries → CI/CD Plan
Week 4+: Development (with clear blueprint)

Invest 3 weeks in design, reduce rework by 60%. "Fast development" without design is the slowest path.


3. Requirements Analysis and Domain Modeling

(1) Core Requirements

Requirement Description Priority
Order creation Customer places order, generates pending order P0
Order payment Confirm order after successful payment P0
Order shipping Warehouse ships, generates tracking number P0
Order cancellation Customer/system cancels order, triggers refund P0
High-value routing Orders > 10,000 USD go through VIP pipeline P1
Batch processing Support 5,000 orders/sec throughput P1
Event sourcing Order status changes produce domain events P2

(2) Domain Model

KOTLIN
// Core domain entities
data class Order(
    val id: String,
    val total: Double,
    var status: OrderStatus,
    val customerId: String,
    val items: List<OrderItem>,
    val createdAt: String,
    var updatedAt: String?
)

data class OrderItem(val sku: String, val quantity: Int, val unitPrice: Double)

sealed class OrderStatus {
    object Pending : OrderStatus()
    data class Processing(val step: Int) : OrderStatus()
    object Paid : OrderStatus()
    object Shipped : OrderStatus()
    object Delivered : OrderStatus()
    data class Cancelled(val reason: String) : OrderStatus()
}

sealed class OrderEvent {
    data class Created(val orderId: String, val total: Double) : OrderEvent()
    data class PaymentReceived(val orderId: String, val amount: Double) : OrderEvent()
    data class Shipped(val orderId: String, val trackingCode: String) : OrderEvent()
    data class Cancelled(val orderId: String, val reason: String) : OrderEvent()
}

(3) Order State Machine

100%
stateDiagram-v2
    [*] --> Pending: Create Order
    Pending --> Processing: Start Processing
    Pending --> Cancelled: Customer Cancel
    Processing --> Paid: Payment Success
    Processing --> Cancelled: Payment Failed
    Paid --> Shipped: Ship Order
    Shipped --> Delivered: Delivery Confirmed
    Delivered --> [*]
    Cancelled --> [*]

4. Layered Architecture

(1) Four-Layer Architecture

100%
flowchart TD
    A[Controller Layer<br/>REST API / Request Validation] --> B[Service Layer<br/>Business Logic / State Machine]
    B --> C[Repository Layer<br/>Data Access / Persistence]
    B --> D[Integration Layer<br/>External API Calls]
    C --> E[(Database)]
    D --> F[Payment Service]
    D --> G[Inventory Service]

(2) Layer Responsibilities

Layer Responsibility Key Technology
Controller HTTP request handling, validation, response Spring WebFlux, suspend
Service Business logic, state machine, event publishing Coroutines, sealed class
Repository Data persistence, queries R2DBC, JPA
Integration External service calls Ktor Client, Resilience4j

(3) Project Module Structure

TEXT
order-processor/
├── build.gradle.kts
├── src/main/kotlin/com/order/
│   ├── Application.kt                    # Spring Boot main
│   ├── controller/
│   │   └── OrderController.kt            # REST endpoints
│   ├── service/
│   │   ├── OrderService.kt               # Business logic
│   │   └── OrderStateMachine.kt          # State transitions
│   ├── repository/
│   │   ├── OrderRepository.kt            # Data access
│   │   └── EventRepository.kt            # Event store
│   ├── integration/
│   │   ├── PaymentClient.kt              # Payment API
│   │   └── InventoryClient.kt            # Inventory API
│   ├── domain/
│   │   ├── Order.kt                      # Entity
│   │   ├── OrderStatus.kt                # Status enum
│   │   └── OrderEvent.kt                 # Domain events
│   ├── config/
│   │   └── AppConfig.kt                  # Configuration
│   └── exception/
│       └── OrderExceptions.kt            # Custom exceptions
└── src/test/kotlin/com/order/
    └── ...                                # Tests

5. Tech Stack Selection

Domain Technology Choice Reason
Framework Spring Boot 3.2 + WebFlux Mature ecosystem, coroutine support
Language Kotlin 1.9 Null safety, coroutines, data class
Async kotlinx.coroutines Structured concurrency, suspend
Serialization kotlinx.serialization Compile-time safety, multi-format
Database PostgreSQL + R2DBC Reactive driver, coroutine-friendly
HTTP Client Ktor Client Kotlin-native, coroutine support
Cache Redis + kotlinx.coroutines Async caching
Testing JUnit 5 + MockK Kotlin-native mocking
Monitoring Micrometer + Prometheus Metrics collection

6. API Design

(1) RESTful Endpoints

KOTLIN
// API Endpoints
// POST   /api/v1/orders              - Create order
// GET    /api/v1/orders              - List orders (with pagination)
// GET    /api/v1/orders/{id}         - Get order by ID
// PATCH  /api/v1/orders/{id}/status  - Update order status
// DELETE /api/v1/orders/{id}         - Cancel order
// GET    /api/v1/orders/{id}/events  - Get order event history

(2) Request/Response DTOs

KOTLIN
// Request
data class CreateOrderRequest(
    val customerId: String,
    val items: List<CreateOrderItemRequest>,
    val shippingAddress: AddressRequest?
)

data class CreateOrderItemRequest(
    val sku: String,
    val quantity: Int,
    val unitPrice: Double
)

// Response
data class OrderResponse(
    val id: String,
    val total: Double,
    val status: String,
    val customerId: String,
    val items: List<OrderItemResponse>,
    val createdAt: String,
    val updatedAt: String?
)

// Error
data class ErrorResponse(
    val code: String,
    val message: String,
    val details: Map<String, String>? = null
)

(3) API Design Principles

Principle Practice
RESTful Resource nouns + HTTP method semantics
Versioning /api/v1/ prefix
Pagination ?page=0&size=20
Filtering ?status=CONFIRMED&customer=C001
HATEOAS Include related links in response (optional)
Error format Unified ErrorResponse

7. Database Schema

SQL
CREATE TABLE orders (
    id          VARCHAR(20) PRIMARY KEY,
    customer_id VARCHAR(20) NOT NULL,
    total       DECIMAL(12,2) NOT NULL DEFAULT 0,
    status      VARCHAR(20) NOT NULL DEFAULT 'PENDING',
    created_at  TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMP
);

CREATE TABLE order_items (
    id          SERIAL PRIMARY KEY,
    order_id    VARCHAR(20) NOT NULL REFERENCES orders(id),
    sku         VARCHAR(50) NOT NULL,
    quantity    INT NOT NULL,
    unit_price  DECIMAL(10,2) NOT NULL
);

CREATE TABLE order_events (
    id          SERIAL PRIMARY KEY,
    order_id    VARCHAR(20) NOT NULL,
    event_type  VARCHAR(30) NOT NULL,
    payload     JSONB,
    created_at  TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_events_order ON order_events(order_id);

8. Complete Example: OrderProcessor Architecture Design Document

KOTLIN
// ============================================
// OrderProcessor - Architecture Design
// Feature: Complete architecture blueprint
// ============================================

// --- Domain Layer ---
data class OrderItem(val sku: String, val qty: Int, val unitPrice: Double) {
    val subtotal: Double get() = qty * unitPrice
}

sealed class OrderStatus { 
    object Pending : OrderStatus()
    object Confirmed : OrderStatus()
    object Shipped : OrderStatus()
    object Delivered : OrderStatus()
    data class Cancelled(val reason: String) : OrderStatus()
}

sealed class OrderEvent {
    data class Created(val orderId: String, val total: Double, val customerId: String) : OrderEvent()
    data class Confirmed(val orderId: String) : OrderEvent()
    data class Shipped(val orderId: String, val trackingCode: String) : OrderEvent()
    data class Delivered(val orderId: String) : OrderEvent()
    data class Cancelled(val orderId: String, val reason: String) : OrderEvent()
}

data class Order(
    val id: String,
    val total: Double,
    var status: OrderStatus,
    val customerId: String,
    val items: List<OrderItem>
)

// --- State Machine ---
object OrderStateMachine {
    fun transition(current: OrderStatus, event: OrderEvent): OrderStatus = when {
        current is OrderStatus.Pending && event is OrderEvent.Created -> OrderStatus.Pending
        current is OrderStatus.Pending && event is OrderEvent.Confirmed -> OrderStatus.Confirmed
        current is OrderStatus.Confirmed && event is OrderEvent.Shipped -> OrderStatus.Shipped
        current is OrderStatus.Shipped && event is OrderEvent.Delivered -> OrderStatus.Delivered
        current is OrderStatus.Pending && event is OrderEvent.Cancelled -> OrderStatus.Cancelled(event.reason)
        current is OrderStatus.Confirmed && event is OrderEvent.Cancelled -> OrderStatus.Cancelled(event.reason)
        else -> throw IllegalStateException("Invalid transition: $current + $event")
    }
}

// --- Architecture Summary ---
fun main() {
    println("=== OrderProcessor Architecture Design ===\n")

    println("Tech Stack:")
    println("  Framework: Spring Boot 3.2 + WebFlux")
    println("  Language:   Kotlin 1.9")
    println("  Async:      kotlinx.coroutines")
    println("  Database:   PostgreSQL + R2DBC")
    println("  Cache:      Redis")
    println("  HTTP:       Ktor Client")
    println("  Testing:    JUnit 5 + MockK")
    println("  Monitoring: Micrometer + Prometheus")

    println("\nOrder Status Machine:")
    val order = Order("ORD-001", 299.99, OrderStatus.Pending, "CUST-001", emptyList())
    val confirmed = OrderStateMachine.transition(order.status, OrderEvent.Confirmed("ORD-001"))
    println("  Pending + Confirmed = $confirmed")

    val shipped = OrderStateMachine.transition(confirmed, OrderEvent.Shipped("ORD-001", "TRK-ABC"))
    println("  Confirmed + Shipped = $shipped")

    val delivered = OrderStateMachine.transition(shipped, OrderEvent.Delivered("ORD-001"))
    println("  Shipped + Delivered = $delivered")

    println("\nAPI Endpoints:")
    println("  POST   /api/v1/orders              - Create order")
    println("  GET    /api/v1/orders              - List orders")
    println("  GET    /api/v1/orders/{id}         - Get order")
    println("  PATCH  /api/v1/orders/{id}/status  - Update status")
    println("  DELETE /api/v1/orders/{id}         - Cancel order")
    println("  GET    /api/v1/orders/{id}/events  - Event history")
}

Output:

TEXT
=== OrderProcessor Architecture Design ===

Tech Stack:
  Framework: Spring Boot 3.2 + WebFlux
  Language:   Kotlin 1.9
  Async:      kotlinx.coroutines
  Database:   PostgreSQL + R2DBC
  Cache:      Redis
  HTTP:       Ktor Client
  Testing:    JUnit 5 + MockK
  Monitoring: Micrometer + Prometheus

Order Status Machine:
  Pending + Confirmed = Confirmed
  Confirmed + Shipped = Shipped
  Shipped + Delivered = Delivered

API Endpoints:
  POST   /api/v1/orders              - Create order
  GET    /api/v1/orders              - List orders
  GET    /api/v1/orders/{id}         - Get order
  PATCH  /api/v1/orders/{id}/status  - Update status
  DELETE /api/v1/orders/{id}         - Cancel order
  GET    /api/v1/orders/{id}/events  - Event history

❓ FAQ

Q Design the database first or the API first?
A Design the domain model first (DDD approach). Then both the API and database are projections of the domain model. The domain model is core; the API and Schema are peripherals.
Q What's the difference between event sourcing and CRUD?
A CRUD only stores current state. Event sourcing stores all state-change events. Event sourcing supports auditing, replay, and time-travel, but has higher complexity. Start with CRUD; add events when needed.
Q R2DBC or JPA — how to choose?
A New project + WebFlux + coroutines → R2DBC (non-blocking). Existing JPA project or team unfamiliar with reactive → JPA + coroutine extensions.
Q How small should a microservice be?
A One microservice per bounded context. OrderProcessor is the "order processing" context — don't stuff payment and inventory into it too.
Q How to handle API versioning?
A URL path versioning (/api/v1/) is simplest and most intuitive. Header-based versioning is more RESTful but more complex. URL versioning is recommended.
Q How to ensure architecture design is implementable?
A Every architectural decision should have a POC (proof of concept) code. Architecture on paper + runnable prototype = implementable design.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Use sealed class to design a state machine for a simple task management system (Todo → InProgress → Done / Cancelled). Hint: sealed class TaskStatus
  2. Intermediate (⭐⭐): Design a complete RESTful API contract with request/response data classes for 5 endpoints. Hint: CreateOrderRequest / OrderResponse
  3. Advanced (⭐⭐⭐): Design a complete layered architecture document for an e-commerce system, including domain model, state machine, API, database schema, and tech stack. Hint: Reference all sections of this lesson

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏