แนวทางออกแบบระบบที่แต่ละ Microservice มีฐานข้อมูลของตนเอง แต่สามารถทำงานร่วมกันผ่าน REST API และ Event-Driven Architecture

#1. เป้าหมายของ Workshop

Workshop นี้ออกแบบระบบ e-Commerce ตัวอย่าง โดยแยกความรับผิดชอบออกเป็น 4 Services

  • User Service — จัดการข้อมูลผู้ใช้
  • Product Service — จัดการสินค้าและราคา
  • Order Service — จัดการคำสั่งซื้อ
  • Payment Service — จัดการการชำระเงิน

แต่ละ Service มีฐานข้อมูลของตัวเองตามหลัก Database per Service และห้าม Service หนึ่งเข้าถึงฐานข้อมูลของอีก Service โดยตรง

Client
  |
  v
Kong API Gateway
  |
  +--> User Service ------> User DB
  +--> Product Service ---> Product DB
  +--> Order Service -----> Order DB
  +--> Payment Service ---> Payment DB
                |
                +---------> Kafka <---------+

#2. หลักการสำคัญ

#2.1 Database per Service

แทนที่จะใช้ฐานข้อมูลกลางร่วมกัน

User Service -----+
Product Service --+--> Shared Database   # ไม่แนะนำสำหรับ Microservices ที่ต้องการ ownership ชัดเจน
Order Service ----+

ให้แต่ละ Service เป็นเจ้าของข้อมูลของตัวเอง

User Service    --> User DB
Product Service --> Product DB
Order Service   --> Order DB
Payment Service --> Payment DB

ข้อดีคือ Service สามารถ deploy, scale, เปลี่ยน schema และเลือก database technology ได้อย่างอิสระมากขึ้น

#3. กฎ Data Ownership

สมมติว่า Order Service ต้องการชื่อสินค้าและราคาสินค้า

ไม่ควรทำแบบนี้

SELECT * FROM product_db.products WHERE id = 1001;

เพราะ Order Service กำลังเข้าถึงฐานข้อมูลของ Product Service โดยตรง

ควรเรียก API

GET /products/1001

หรือรับข้อมูลผ่าน Domain Event เช่น

{
  "eventType": "ProductPriceChanged",
  "productId": 1001,
  "price": 590.00
}

#4. API Gateway

Client ไม่จำเป็นต้องรู้ address ของแต่ละ Service

Client
   |
   v
API Gateway
   |
   +-- /users/* -----> User Service
   +-- /products/* --> Product Service
   +-- /orders/* ----> Order Service
   +-- /payments/* --> Payment Service

API Gateway สามารถรับผิดชอบงาน cross-cutting concerns เช่น

  • Routing
  • Authentication / Authorization
  • Rate Limiting
  • CORS
  • Logging
  • Metrics
  • Load Balancing

ไม่ควรนำ business logic หลักของระบบไปไว้ใน Gateway

#5. การสื่อสารระหว่าง Services

ระบบ Microservices มักใช้ทั้ง synchronous และ asynchronous communication

#Synchronous

ใช้ REST หรือ gRPC เมื่อ Service ต้องการผลลัพธ์ทันที

Order Service
     |
     | GET /products/1001
     v
Product Service

ตัวอย่าง response

{
  "id": 1001,
  "name": "Mechanical Keyboard",
  "price": 2590,
  "stock": 20
}

ข้อเสียคือ runtime coupling: หาก Product Service ล่ม Order Service อาจไม่สามารถดำเนินงานบางส่วนต่อได้ จึงควรพิจารณา timeout, retry อย่างจำกัด และ circuit breaker

#Asynchronous

ใช้ Message Broker เช่น Apache Kafka หรือ RabbitMQ

Order Service
     |
     | OrderCreated
     v
Kafka
     |
     +------> Payment Service
     +------> Inventory Service
     +------> Notification Service

Producer ไม่จำเป็นต้องรอให้ทุก Consumer ทำงานเสร็จ

#6. ตัวอย่าง Order Workflow

ผู้ใช้ส่งคำสั่ง

POST /orders

Request

{
  "userId": 101,
  "items": [
    {
      "productId": 1001,
      "quantity": 2
    }
  ]
}

Order Service สามารถตรวจสอบข้อมูลที่จำเป็นจาก Product Service แล้วบันทึก Order ลง Order DB

orders
---------------------------------
id      ORD-001
user_id 101
status  PENDING
amount  5180

จากนั้น publish event

{
  "eventId": "evt-001",
  "eventType": "OrderCreated",
  "orderId": "ORD-001",
  "userId": 101,
  "amount": 5180
}

#7. Payment Service รับ Event

Payment Service subscribe OrderCreated

Kafka
  |
  | OrderCreated
  v
Payment Service

Payment Service บันทึกข้อมูลลง Payment DB ของตนเอง

payments
---------------------------------
id       PAY-001
order_id ORD-001
amount   5180
status   SUCCESS

เมื่อสำเร็จ publish

{
  "eventId": "evt-002",
  "eventType": "PaymentCompleted",
  "orderId": "ORD-001",
  "paymentId": "PAY-001",
  "amount": 5180
}

Order Service consume event แล้วเปลี่ยนสถานะ Order

PENDING --> PAID

นี่คือ Eventual Consistency: ข้อมูลในทุก Service ไม่จำเป็นต้องเปลี่ยนพร้อมกันใน transaction เดียว แต่จะเข้าสู่สถานะสอดคล้องกันหลังจาก events ถูกประมวลผล

#8. ปัญหา Distributed Transaction

Monolith อาจใช้ transaction เดียวได้

BEGIN
  INSERT order
  UPDATE inventory
  INSERT payment
COMMIT

เมื่อข้อมูลกระจายอยู่หลายฐานข้อมูล การใช้ transaction แบบเดียวกันครอบทุก Service ไม่ใช่แนวทางหลักของ Microservices

Order DB
Product/Inventory DB
Payment DB

จึงมักใช้ Saga Pattern

#9. Saga Pattern

ตัวอย่าง Saga

Create Order
    |
    v
Reserve Inventory
    |
    v
Process Payment
    |
    v
Confirm Order

กรณี Payment ล้มเหลว

PaymentFailed
      |
      +--> Release Inventory
      |
      +--> Cancel Order

การทำงานย้อนกลับนี้เรียกว่า Compensating Transaction

Saga มีสองรูปแบบหลัก

#Choreography

Services สื่อสารกันด้วย Events

OrderCreated
     |
     v
InventoryReserved
     |
     v
PaymentCompleted
     |
     v
OrderConfirmed

เหมาะกับ workflow ที่ไม่ซับซ้อนมากและต้องการลด central coordinator

#Orchestration

มี Saga Orchestrator ควบคุม workflow

              Saga Orchestrator
              /       |       \
             v        v        v
          Order    Inventory  Payment

เหมาะกับ workflow ที่มีหลายขั้นตอน เงื่อนไข และ compensation ซับซ้อน

#10. Transactional Outbox

มี failure case สำคัญดังนี้

1. INSERT Order สำเร็จ
2. Application crash
3. Publish OrderCreated ไม่สำเร็จ

Order DB มีข้อมูล แต่ Service อื่นไม่เคยได้รับ event

Transactional Outbox แก้ปัญหาด้วยการบันทึก business data และ event ลงฐานข้อมูลเดียวกันภายใต้ local transaction เดียว

BEGIN

INSERT INTO orders (...);
INSERT INTO outbox_events (...);

COMMIT

จากนั้น Outbox Publisher อ่าน event แล้วส่งไป Kafka

Order Service
     |
     +--> Order DB
            |
            +--> orders
            +--> outbox_events
                      |
                      v
               Outbox Publisher
                      |
                      v
                    Kafka

หลัง publish สำเร็จจึง mark event ว่าส่งแล้ว

#11. Idempotency

Message Broker อาจส่ง message ซ้ำได้ ดังนั้น Consumer ควรออกแบบให้ประมวลผล event เดิมซ้ำแล้วไม่สร้างผลกระทบซ้ำ

ตัวอย่าง event มี eventId

{
  "eventId": "evt-002",
  "eventType": "PaymentCompleted",
  "orderId": "ORD-001"
}

Consumer ตรวจสอบว่า event เคยประมวลผลหรือยัง

SELECT event_id
FROM processed_events
WHERE event_id = 'evt-002';

ถ้ามีแล้วให้ ignore

แนวคิดนี้เรียกว่า Idempotent Consumer

#12. โครงสร้าง Docker Compose สำหรับ Workshop

โครงสร้างโปรเจกต์ตัวอย่าง

microservices-demo/
|
+-- docker-compose.yml
+-- kong/
+-- user-service/
+-- product-service/
+-- order-service/
+-- payment-service/

ตัวอย่าง docker-compose.yml แบบย่อ

services:

  user-db:
    image: postgres:17
    environment:
      POSTGRES_DB: userdb
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret

  product-db:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: productdb
      MYSQL_USER: app
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: rootsecret

  order-db:
    image: postgres:17
    environment:
      POSTGRES_DB: orderdb
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret

  payment-db:
    image: postgres:17
    environment:
      POSTGRES_DB: paymentdb
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret

  kafka:
    image: apache/kafka:latest

  user-service:
    build: ./user-service
    depends_on:
      - user-db

  product-service:
    build: ./product-service
    depends_on:
      - product-db

  order-service:
    build: ./order-service
    depends_on:
      - order-db
      - kafka

  payment-service:
    build: ./payment-service
    depends_on:
      - payment-db
      - kafka

สำหรับ production ควร pin image versions, ใช้ secrets manager, health checks และกำหนด resource limits แทนการพึ่ง tag latest

#13. ตัวอย่าง API

#User Service

POST /users
GET /users/{id}
PUT /users/{id}

#Product Service

POST /products
GET /products
GET /products/{id}
PUT /products/{id}

#Order Service

POST /orders
GET /orders/{id}
GET /users/{userId}/orders

#Payment Service

POST /payments
GET /payments/{id}
GET /orders/{orderId}/payment

#14. Kong Routing

ตัวอย่าง public routes

http://localhost:8000/users
http://localhost:8000/products
http://localhost:8000/orders
http://localhost:8000/payments

Kong route ไปยัง internal services

/users     --> user-service
/products  --> product-service
/orders    --> order-service
/payments  --> payment-service

Client จึงไม่จำเป็นต้องทราบ host/port ภายในของแต่ละ Service

#15. Security

Architecture สามารถเพิ่ม Identity Provider เช่น Keycloak

Client
  |
  | Login
  v
Keycloak
  |
  | JWT
  v
Client
  |
  | Authorization: Bearer <token>
  v
Kong
  |
  v
Microservices

ควรแยก Authentication และ Authorization ให้ชัดเจน และตรวจสอบสิทธิ์ที่ระดับ Service สำหรับ business authorization ที่เฉพาะเจาะจง

#16. Observability

Microservices มี request กระจายหลาย process จึงควรมี observability ตั้งแต่ต้น

Logs    --> Loki / ELK
Metrics --> Prometheus --> Grafana
Traces  --> OpenTelemetry --> Jaeger/Tempo

ควรส่ง correlation/trace ID ผ่านทุก Service

traceparent: 00-...

เพื่อ trace request เช่น

Gateway
  -> Order Service
       -> Product Service
       -> Kafka
            -> Payment Service

#17. Reliability Patterns ที่ควรเพิ่ม

ระบบ production ควรพิจารณา

  • Timeout
  • Retry with backoff เฉพาะ operation ที่ปลอดภัย
  • Circuit Breaker
  • Bulkhead
  • Dead Letter Queue (DLQ)
  • Idempotency Key
  • Transactional Outbox
  • Schema/version compatibility สำหรับ Events
  • Health Check / Readiness Check

ตัวอย่าง client request ที่มี idempotency key

POST /payments
Idempotency-Key: 79cbbe8e-9e72-4b58-9025-example

ช่วยป้องกันการสร้าง Payment ซ้ำจากการ retry

#18. Anti-Patterns

#Shared Database

Service A ----+
Service B ----+--> Database เดียว
Service C ----+

ทำให้ schema coupling สูงและ Service ไม่ได้เป็นเจ้าของข้อมูลจริง

#Direct Database Access

Order Service ----SQL----> Product DB

ควรเป็น

Order Service ----API----> Product Service ----> Product DB

#Distributed Join

ไม่ควรพยายาม JOIN ตารางข้ามฐานข้อมูลของ Services เหมือน Monolith

สำหรับ read-heavy use case สามารถสร้าง Read Model / CQRS projection จาก events ได้

User Event -----+
Order Event ----+--> Reporting Read Model
Payment Event --+

Reporting Service จึง query ข้อมูลที่ denormalized ไว้สำหรับการอ่านได้โดยไม่ทำลาย ownership ของ operational databases

#19. Architecture ที่แนะนำ

                         +----------------+
                         | Identity       |
                         | Provider       |
                         +-------+--------+
                                 |
                                 | JWT/OAuth2
                                 v
+--------+              +--------+---------+
| Client |------------->| Kong API Gateway |
+--------+              +--------+---------+
                                 |
             +-------------------+-------------------+
             |                   |                   |
             v                   v                   v
       +-----------+       +-----------+       +-----------+
       | User      |       | Product   |       | Order     |
       | Service   |       | Service   |       | Service   |
       +-----+-----+       +-----+-----+       +-----+-----+
             |                   |                   |
             v                   v                   v
          User DB            Product DB           Order DB
                                                     |
                                                     | Events
                                                     v
                                               +-----------+
                                               | Kafka     |
                                               +-----+-----+
                                                     |
                                                     v
                                               +-----------+
                                               | Payment   |
                                               | Service   |
                                               +-----+-----+
                                                     |
                                                     v
                                                Payment DB

#20. แนวทางเลือก Communication

Requirement แนวทาง
ต้องการ response ทันที REST / gRPC
แจ้งหลาย Services Event
งาน background Queue / Event
Workflow ข้ามหลายฐานข้อมูล Saga
ป้องกัน DB commit แล้ว event หาย Transactional Outbox
ป้องกัน message ซ้ำ Idempotency
Query ข้อมูลรวมจำนวนมาก CQRS / Read Model

#21. สรุป

Microservices ที่มีฐานข้อมูลแยกกันยังสามารถทำงานร่วมกันได้โดยไม่ต้องแชร์ database

หลักสำคัญคือ

Database per Service
        +
REST / gRPC
        +
Event-Driven Architecture
        +
Saga Pattern
        +
Transactional Outbox
        +
Idempotency
        +
Observability

เป้าหมายไม่ใช่เพียงการ “แยก Service” แต่คือการกำหนด Bounded Context, Data Ownership และ Contract ให้ชัดเจน เพื่อให้แต่ละ Service สามารถพัฒนา deploy และ scale ได้โดยลด coupling ระหว่างกัน

#Workshop ถัดไป

สามารถต่อยอดตัวอย่างนี้เป็นระบบที่รันจริงด้วย

Kong API Gateway
+ Laravel / Spring Boot / Node.js
+ PostgreSQL / MySQL
+ Apache Kafka
+ Docker Compose
+ Keycloak
+ Prometheus / Grafana
+ OpenTelemetry

จากนั้นทดสอบ flow:

Create User
    -> Create Product
    -> Create Order
    -> OrderCreated Event
    -> Process Payment
    -> PaymentCompleted Event
    -> Confirm Order

ซึ่งเป็นพื้นฐานสำคัญสำหรับการออกแบบ Microservices ที่มีฐานข้อมูลแยกกันในระบบ Production