Architecture Design Patterns Microservices

Web Architecture & Design Patterns — ออกแบบระบบให้ Scale ได้และดูแลง่าย 🏗️

By Anirach Mingkhwan DevOps & Vibe Coding 2026
Web Architecture & Design Patterns

🐕 ทำไมต้องรู้ Architecture?

Architecture เป็นหัวใจสำคัญของการพัฒนาซอฟต์แวร์ที่หลายคนมองข้าม เพราะมันไม่ใช่แค่เรื่องของการเขียนโค้ดให้ทำงานได้ แต่เป็นการออกแบบโครงสร้างที่จะทำให้ระบบของเราสามารถ scale ได้ ดูแลรักษาได้ และพัฒนาต่อยอดได้ในระยะยาว

ความแตกต่างระหว่างโค้ดที่ดีกับ architecture ที่ดีนั้นเปรียบเสมือนความแตกต่างระหว่างการสร้างบ้านที่สวยงามกับการวางแปลนบ้านที่ดี บ้านที่สวยแต่แปลนแย่อาจพังได้ง่าย แต่บ้านที่แปลนดีแม้จะตกแต่งไม่สวยก็ยังแก้ไขและปรับปรุงได้

❌ ไม่คิด Architecture:
  • 1 ไฟล์ 10,000 บรรทัด
  • แก้จุดนึง → พังอีก 5 จุด
  • Deploy ทีนึง → ลุ้นทั้งทีม
  • Scale ไม่ได้ → server ล่มตอน peak
  • Developer ใหม่ต้อง 3 เดือนกว่าจะเข้าใจ
✅ คิด Architecture:
  • แต่ละส่วนแยกชัดเจน
  • แก้จุดนึง → ไม่กระทบจุดอื่น
  • Deploy ทีละส่วนได้
  • Scale เฉพาะส่วนที่ต้องการ
  • Developer ใหม่เข้าใจภายใน 1 สัปดาห์
Code ดี + Architecture แย่ = ระบบพัง
Code แย่ + Architecture ดี = แก้ได้ทีหลัง

🏢 Monolith vs Microservices

การเลือกระหว่าง Monolith กับ Microservices เป็นหนึ่งในคำถามที่ถูกถามบ่อยที่สุดในยุคนี้ หลายคนคิดว่า microservices เป็น "silver bullet" ที่จะแก้ปัญหาทุกอย่างได้ แต่ความจริงแล้วแต่ละแบบมี trade-offs ของตัวเอง และการเลือกใช้ต้องดูจากบริบทของ project และทีมงาน

บริษัทส่วนใหญ่จะเริ่มต้นด้วย monolith เพราะเป็นแบบที่เข้าใจง่าย พัฒนาเร็ว และ deploy ไม่ซับซ้อน แล้วค่อยแยกเป็น microservices เมื่อทีมและระบบใหญ่ขึ้น

Monolith — ทุกอย่างอยู่ก้อนเดียว

Monolith คือสถาปัตยกรรมที่รวม feature ทั้งหมดของแอปไว้ใน codebase เดียว deploy ทีเดียว run ใน process เดียว ใช้ database ร่วมกัน เหมาะกับ startup หรือทีมเล็กที่ต้องการ ship feature เร็วๆ

Monolith Architecture
Single Application
Auth Module
Users Module
Products Module
Orders Module
Payments Module
Notifications Module
Shared Database (1)
  • Deploy ทีเดียว ทั้งก้อน
  • Scale ทีเดียว ทั้งก้อน
  • พังทีเดียว... ก็ทั้งก้อน 💀
Microservices Architecture
Auth Service
Node.js:3001
Redis
Users Service
Python:3002
PostgreSQL
Products Service
Go:3003
MongoDB
Orders Service
Java:3004
PostgreSQL
  • แต่ละ service: Deploy อิสระ
  • Scale อิสระ
  • DB แยก (Database per service)
  • ภาษา/tech stack อิสระ
  • ทีมดูแลอิสระ

Comparison Table

Monolith Microservices
Complexity ⭐ Simple ⭐⭐⭐ Complex
Development ⚡ Fast start 🐢 Slower setup
Deployment All or nothing Independent
Scaling Scale everything Scale per service
Tech stack One stack Mix & match
Data consistency ✅ Easy (1 DB) ⚠️ Eventual
Debugging ✅ Stack trace ⚠️ Distributed
Team size 1-15 devs 10-100+ devs
Fault isolation ❌ One bug = entire app down ✅ Service fails others survive
Communication Function calls (fast) Network (HTTP, gRPC, queues)
DevOps needs ⭐ Basic ⭐⭐⭐ K8s, etc.
Cost 💰 Lower 💰💰💰 Higher
💡 Start with Monolith → Extract to Microservices when you actually NEED to

"Don't start with microservices unless you have a very good reason." — Martin Fowler

Microservices เป็นการแบ่งแอปพลิเคชันออกเป็น service เล็กๆ หลายตัวที่ทำงานแยกจากกัน แต่ละ service มีหน้าที่เฉพาะ มี database ของตัวเอง และสื่อสารกันผ่าน network (HTTP, gRPC, message queues)

ข้อดีคือแต่ละ service scale ได้อิสระ ใช้ tech stack ต่างกันได้ ทีมทำงานแยกกัน และถ้า service หนึ่งล่มก็ไม่กระทบ service อื่น แต่ข้อเสียคือความซับซ้อนในการ deploy การ debug และ network latency

Modular Monolith — Best of Both Worlds

Modular Monolith เป็น compromise ที่ดีระหว่าง simplicity ของ monolith กับ modularity ของ microservices โดยแบ่ง code เป็น module ที่มีขอบเขตชัดเจน แต่ยัง deploy เป็นก้อนเดียว

แต่ละ module จะมี API interface ของตัวเอง ไม่เข้าถึง database ของ module อื่นโดยตรง และสามารถแยกออกเป็น microservice ได้ง่ายในอนาคต นี่คือสิ่งที่ Martin Fowler แนะนำว่า "เริ่มด้วย monolith แล้วค่อยแยก"

Modular Monolith (แนะนำ!)
Single Deployment
Auth Module
Routes, Models, Logic
Users Module
Routes, Models, Logic
Products Module
Routes, Models, Logic
Orders Module
Routes, Models, Logic
Communicate via Internal APIs only
(NOT direct DB access)
Shared Database
(but each module owns its tables)
  • ✅ Simple deployment (1 unit)
  • ✅ Clear module boundaries
  • ✅ Easy to extract to microservice later
  • ✅ No network overhead between modules
  • ✅ Single DB = ACID transactions
src/
  modules/
    auth/
      auth.controller.ts
      auth.service.ts
      auth.routes.ts
      auth.model.ts
      auth.types.ts
    users/
      users.controller.ts
      users.service.ts
      users.routes.ts
      users.model.ts
      users.types.ts
    products/
      products.controller.ts
      products.service.ts
      products.routes.ts
      ...
  shared/
    middleware/
    database/
    utils/
  app.ts
  server.ts

📐 Design Patterns — MVC, MVVM, Clean Architecture

Design patterns เป็นแนวทางในการจัดระเบียบ code ให้มีโครงสร้างที่ชัดเจน แยกหน้าที่กันได้ดี และดูแลรักษาได้ง่าย แต่ละ pattern จะเหมาะกับ use case ที่แตกต่างกัน และมีระดับความซับซ้อนที่แตกต่างกัน

การเลือก design pattern ควรคำนึงถึงขนาดของโปรเจกต์ ความซับซ้อนของ business logic และขนาดของทีม อย่าเอา Clean Architecture มาใช้กับโปรเจกต์ CRUD เล็กๆ เพราะจะได้แต่ complexity เปล่าๆ

MVC (Model-View-Controller)

MVC เป็น pattern พื้นฐานที่แยกแอปพลิเคชันเป็น 3 ส่วน: Model (ข้อมูลและ business logic), View (การแสดงผล), และ Controller (จัดการ user input และควบคุมการทำงาน)

เหมาะกับ web application ที่ render หน้าเว็บฝั่ง server เช่น Rails, Django, Laravel หรือ Express.js ที่ใช้ template engine อย่าง EJS แต่สำหรับ API จะเป็นแค่ Controller กับ Model เพราะไม่มี View (ส่ง JSON กลับไป)

MVC Pattern Flow
View
(Template)
HTML/EJS
←→
Controller
(Logic)
Routes + Handlers
Model
(Data + Business Logic)
Examples: Rails, Django, Laravel, Express + EJS
Best for: Server-rendered (SSR) web apps
// ═══════════════════════════════
// MVC Example — Express.js
// ═══════════════════════════════

// Model — Database interaction
// models/Order.ts
class OrderModel {
  static async create(data: OrderInput): Promise<Order> {
    const result = await db.query(
      'INSERT INTO orders (user_id, total, status) VALUES ($1, $2, $3) RETURNING *',
      [data.userId, data.total, 'pending']
    );
    return result.rows[0];
  }

  static async findByUser(userId: string): Promise<Order[]> {
    return db.query('SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC', [userId]);
  }
}

// Controller — Business logic
// controllers/orderController.ts
class OrderController {
  static async createOrder(req: Request, res: Response) {
    try {
      const { items } = req.body;
      
      // Business logic
      const total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
      const order = await OrderModel.create({ userId: req.user.id, total });
      
      // Respond
      res.status(201).json({ order });
    } catch (err) {
      res.status(500).json({ error: 'Failed to create order' });
    }
  }
}

// View — Routes (API = JSON response)
// routes/orders.ts
router.post('/orders', authenticate, OrderController.createOrder);
router.get('/orders', authenticate, OrderController.listOrders);

Layered Architecture — Controller → Service → Repository

Layered Architecture หรือ N-Tier Architecture เป็นการแบ่ง application เป็น layer ที่มีหน้าที่ชัดเจน แต่ละ layer จะเรียกใช้แค่ layer ที่อยู่ข้างล่าง ไม่ข้าม layer และไม่เรียกกลับขึ้นไป

Pattern นี้เหมาะกับ business application ที่มี business logic ซับซ้อน เพราะแยก concerns ได้ชัดเจน ทำให้ test ได้ง่าย แก้ไขได้ง่าย และทีมงานแยกงานกันได้ดี แต่ถ้า logic ไม่ซับซ้อนอาจจะ overkill

Layered (N-Tier) Architecture
Presentation Layer (Controllers / Routes)
→ HTTP requests, validation, response format
Service Layer (Business Logic)
→ Business rules, orchestration, validation
Repository Layer (Data Access)
→ Database queries, ORM, external APIs
Database / External Services
กฎ: แต่ละ layer เรียกได้แค่ layer ข้างล่าง
Controller → Service ✅
Controller → Repository ❌ (ข้าม layer)
Service → Controller ❌ (ย้อนกลับ)
// ═══════════════════════════════
// Layered Architecture Example
// ═══════════════════════════════

// Repository Layer — Data access only
// repositories/orderRepository.ts
class OrderRepository {
  async create(data: CreateOrderDTO): Promise<Order> {
    return prisma.order.create({ data });
  }

  async findById(id: string): Promise<Order | null> {
    return prisma.order.findUnique({ 
      where: { id },
      include: { items: true, user: true }
    });
  }

  async findByUser(userId: string): Promise<Order[]> {
    return prisma.order.findMany({
      where: { userId },
      orderBy: { createdAt: 'desc' }
    });
  }
}

// Service Layer — Business logic
// services/orderService.ts
class OrderService {
  constructor(
    private orderRepo: OrderRepository,
    private productRepo: ProductRepository,
    private paymentService: PaymentService,
    private notificationService: NotificationService,
  ) {}

  async createOrder(userId: string, items: CartItem[]): Promise<Order> {
    // 1. Validate stock
    for (const item of items) {
      const product = await this.productRepo.findById(item.productId);
      if (!product) throw new NotFoundError(`Product ${item.productId} not found`);
      if (product.stock < item.quantity) throw new ValidationError(`Insufficient stock for ${product.name}`);
    }

    // 2. Calculate total
    const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);

    // 3. Create order
    const order = await this.orderRepo.create({
      userId,
      total,
      status: 'pending',
      items: items.map(i => ({
        productId: i.productId,
        quantity: i.quantity,
        price: i.price,
      })),
    });

    // 4. Reserve stock
    for (const item of items) {
      await this.productRepo.decrementStock(item.productId, item.quantity);
    }

    // 5. Send notification
    await this.notificationService.sendOrderConfirmation(userId, order);

    return order;
  }
}

// Controller Layer — HTTP handling
// controllers/orderController.ts
class OrderController {
  constructor(private orderService: OrderService) {}

  createOrder = async (req: Request, res: Response) => {
    try {
      const { items } = req.body;
      
      // Input validation only
      if (!items?.length) {
        return res.status(400).json({ error: 'Items required' });
      }

      // Delegate to service
      const order = await this.orderService.createOrder(req.user.id, items);
      
      res.status(201).json({ data: order });
    } catch (err) {
      if (err instanceof ValidationError) {
        return res.status(400).json({ error: err.message });
      }
      if (err instanceof NotFoundError) {
        return res.status(404).json({ error: err.message });
      }
      res.status(500).json({ error: 'Internal server error' });
    }
  };
}

Clean Architecture / Hexagonal Architecture

Clean Architecture (หรือ Hexagonal Architecture) เป็น pattern ที่เน้นการแยก business logic ออกจาก external dependencies เช่น database, web framework, external APIs โดยใช้หลักการ "Dependency Inversion"

หลักสำคัญคือ "Dependency Rule" - dependencies ต้องชี้เข้าข้างในเท่านั้น inner layers ไม่รู้จัก outer layers เลย ทำให้ business logic independent จาก framework และ database ทดสอบได้ง่าย แต่ code จะเยอะขึ้นและซับซ้อนกว่า

เหมาะกับโปรเจกต์ขนาดใหญ่ที่มี business rules ซับซ้อน ต้องการ testability สูง หรือต้องการเปลี่ยน technology stack บ่อย แต่ถ้าทำ CRUD ธรรมดาอาจจะเป็น overkill

Clean Architecture (Onion)
Frameworks & Drivers
Express, PostgreSQL, Redis, AWS S3
Interface Adapters
Controllers, Presenters, Gateways
Enterprise
Entities
Domain Models
Dependency Rule:
  • dependencies point INWARD only
  • Inner layers know NOTHING about outer layers
  • Use interfaces/ports at boundaries
✅ Testable: mock outer layers
✅ Framework-independent: swap Express → Fastify
✅ DB-independent: swap PostgreSQL → MongoDB
⚠️ More code & complexity

🌐 API Styles — REST vs GraphQL vs gRPC

การเลือก API style เป็นการตัดสินใจสำคัญที่จะกระทบกับการพัฒนา frontend, mobile app, และ service-to-service communication แต่ละ style มี strengths และ use cases ที่แตกต่างกัน และไม่จำเป็นต้องเลือกแค่อันเดียว

หลายบริษัทใช้หลาย style พร้อมกัน เช่น REST สำหรับ public API, GraphQL สำหรับ mobile app, และ gRPC สำหรับ internal services การเข้าใจข้อดีข้อเสียของแต่ละแบบจะช่วยให้เลือกได้ถูกต้อง

REST — Resource-based

REST (Representational State Transfer) เป็น architectural style ที่ใช้ HTTP methods (GET, POST, PUT, DELETE) กับ URLs เพื่อจัดการ resources เป็น standard ที่เข้าใจง่าย มีเครื่องมือสนับสนุนเยอะ และ cache ได้ดีผ่าน HTTP

แต่ REST มีปัญหาในเรื่อง over-fetching (ได้ข้อมูลมาเกินความต้องการ), under-fetching (ต้อง call หลาย endpoints), และ N+1 problem ที่เกิดจากการ fetch related data แยกกัน

REST API
HTTP Method + URL = Operation on Resource
GET /api/users
List users
GET /api/users/123
Get user 123
POST /api/users
Create user
PUT /api/users/123
Update user 123 (full)
DELETE /api/users/123
Delete user 123
Nested resources:
GET /api/users/123/posts
GET /api/posts/456/comments
REST Problems:
  • Over-fetching — ขอ user แต่ได้ทุก field กลับมา
  • Under-fetching — ต้อง call หลาย endpoints
  • N+1 problem — list ที่ต้อง fetch related data

GraphQL — Query-based

GraphQL แก้ปัญหาของ REST โดยให้ client สามารถระบุได้ว่าต้องการข้อมูลอะไรบ้าง ผ่าน single endpoint เพียงแค่ POST /graphql เดียว client เขียน query บอกว่าต้องการ fields ไหนบ้าง server ก็ส่งแค่ส่วนนั้นกลับไป

ข้อดีคือแก้ปัญหา over/under-fetching ได้ลด API calls ลง strongly typed schema และมี introspection สำหรับ development tools แต่ข้อเสียคือ caching ยาก complexity สูงกว่า และต้องเรียนรู้ GraphQL query language

GraphQL API
POST /graphql
Single endpoint
Client บอกว่าต้องการ data อะไร
Query:
{
  user(id: 123) {
    name
    avatar
    posts(limit: 5) {
      title
      likes
    }
  }
}
Response:
{
  "data": {
    "user": {
      "name": "Alice",
      "avatar": "url...",
      "posts": [...]
    }
  }
}
1 request! เลือกเฉพาะ fields ที่ต้องการ!
# ═══════════════════════════════
# GraphQL Schema Definition
# ═══════════════════════════════

type User {
  id: ID!
  name: String!
  email: String!
  avatar: String
  posts(limit: Int = 10, offset: Int = 0): [Post!]!
  followers: Int!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
  likes: Int!
  createdAt: DateTime!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
  posts(status: PostStatus, limit: Int): [Post!]!
}

type Mutation {
  createPost(input: CreatePostInput!): Post!
  updatePost(id: ID!, input: UpdatePostInput!): Post!
  deletePost(id: ID!): Boolean!
}

# ═══════════════════════════════
# Client Queries
# ═══════════════════════════════

# Dashboard — 1 request แทน 4 requests
query Dashboard {
  me: user(id: "123") {
    name
    avatar
    followers
  }
  recentPosts: posts(limit: 5) {
    title
    likes
    createdAt
  }
}

# Mutation
mutation CreatePost {
  createPost(input: {
    title: "GraphQL is Awesome"
    content: "Here's why..."
  }) {
    id
    title
    createdAt
  }
}

gRPC — Binary Protocol (Service-to-Service)

gRPC เป็น high-performance RPC framework ที่ใช้ Protocol Buffers (protobuf) เป็น serialization format แทน JSON ทำให้เร็วกว่ามาก strongly typed จาก proto definition และสนับสนุน streaming สำหรับ real-time communication

เหมาะมากสำหรับ service-to-service communication ใน microservices เพราะ performance ดี type safety และ code generation แต่ไม่เหมาะกับ browser เพราะต้องใช้ grpc-web proxy และ learning curve สูงกว่า REST

gRPC
  • Binary protocol (Protocol Buffers) → 10x faster
  • Strongly typed → contract-first development
  • Streaming support → real-time data
  • Code generation → auto-generate client/server
Service A
(Node.js)
gRPC
(binary)
Service B
(Go)
vs
Service A
HTTP/JSON
(text, slow)
Service B
// ═══════════════════════════════
// user.proto — Protocol Buffer Definition
// ═══════════════════════════════

syntax = "proto3";
package user;

service UserService {
  // Unary — request/response เดียว
  rpc GetUser (GetUserRequest) returns (User);
  rpc CreateUser (CreateUserRequest) returns (User);
  
  // Server streaming — server ส่ง stream กลับ
  rpc ListUsers (ListUsersRequest) returns (stream User);
  
  // Bidirectional streaming — ทั้ง 2 ฝั่ง stream
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  UserRole role = 4;
  int64 created_at = 5;
}

enum UserRole {
  MEMBER = 0;
  EDITOR = 1;
  ADMIN = 2;
}

message GetUserRequest {
  string id = 1;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  string password = 3;
}

REST vs GraphQL vs gRPC — เลือกยังไง?

REST GraphQL gRPC
Format JSON (text) JSON (text) Protobuf (binary)
Transport HTTP/1.1 HTTP/1.1 HTTP/2
Performance ⭐⭐ ⭐⭐ ⭐⭐⭐
Type Safety ❌ Manual ✅ Schema ✅ Proto
Browser Support ✅ Native ✅ Library ⚠️ grpc-web
Over/Under fetch ❌ Common ✅ Exact ✅ Exact
Caching ✅ HTTP cache ⚠️ Complex ❌ Hard
Learning Curve ⭐ Easy ⭐⭐ Medium ⭐⭐⭐ Hard
Best For Public APIs, CRUD, simple Mobile/SPA, complex UI Microservice internal
💡 Common patterns:
├── Public API → REST (simple, cacheable, everyone knows it)
├── Frontend (SPA/Mobile) → GraphQL (flexible queries)
├── Service-to-service → gRPC (fast, typed, streaming)
└── Mix! REST for public + gRPC for internal is common

📨 Event-Driven Architecture & Message Queues

Event-Driven Architecture เป็นการออกแบบระบบให้ทำงานแบบ asynchronous ผ่าน events และ message queues แทนการเรียก API แบบ synchronous ทำให้ระบบมี resilience สูงขึ้น scale ได้ดีกว่า และไม่ล่มทั้งระบบเมื่อ service ใดบาง service หนึ่งมีปัญหา

ประโยชน์หลักคือ loose coupling ระหว่าง services, fault tolerance เพราะ message queues จะ buffer messages ไว้, และ scalability เพราะแต่ละ service สามารถ process messages ในอัตราของตัวเองได้

Synchronous vs Asynchronous

ให้ดูตัวอย่างการสั่งซื้อสินค้า ในแบบ synchronous user กดสั่งซื้อแล้วต้องรอให้ระบบ create order, charge payment, update inventory, send email, send SMS, update analytics, และ generate invoice เสร็จทั้งหมดก่อนถึงจะได้ response

แต่ในแบบ asynchronous หลังจาก create order และ charge payment เสร็จแล้ว ระบบจะ publish event "order.created" ไปยัง message queue แล้ว respond กลับไปทันที ส่วนงานอื่นๆ อย่าง email, SMS, analytics จะทำใน background โดย consumers ที่ subscribe event นั้น

❌ Synchronous (blocking)
User clicks "Place Order"
├── Create order .......... 100ms
├── Charge payment ........ 500ms ← 3rd party!
├── Update inventory ...... 100ms
├── Send email ............ 300ms ← 3rd party!
├── Send SMS .............. 400ms ← 3rd party!
├── Update analytics ...... 100ms
└── Generate invoice ...... 200ms
Total: 1,700ms ← User waits 1.7 seconds! 😤
⚠️ ถ้า email server ล่ม → ทั้ง order fail!
✅ Asynchronous (event-driven)
User clicks "Place Order"
├── Create order .......... 100ms
├── Charge payment ........ 500ms
├── Publish event: "order.created" → Queue
└── Response: "Order placed!" ← 600ms total! ⚡
Message Queue processes (background):
├── Send email .............. async
├── Send SMS ................ async
├── Update analytics ........ async
└── Generate invoice ........ async
⚡ User gets response in 600ms!
✅ Email ล่ม → retry อัตโนมัติ → order ไม่ fail

Message Queue Architecture

Message Queue เป็นตัวกลางที่เก็บ messages ไว้ชั่วคราว โดย Producer (service ที่ส่ง message) จะส่ง message เข้า queue, และ Consumer (service ที่รับ message) จะดึง message ออกมาประมวลผล

ข้อดีคือถ้า consumer ล่ม message ยังคงอยู่ใน queue ไม่สูญหาย และสามารถมี consumer หลายตัวมาแย่งงานกันได้ (competing consumers) ทำให้ scale ได้ดี Popular message queues ได้แก่ RabbitMQ, Apache Kafka, Redis Pub/Sub, AWS SQS

Message Queue Pattern
Producer
(ผู้ส่ง)

Order Service
order.created queue
message 1
message 2
message 3
Consumer
(ผู้รับ)

Email Service
SMS Service
Analytics
How it works:
  1. Producer ส่ง message เข้า queue
  2. Queue เก็บ message ไว้ (persistent)
  3. Consumer ดึง message มาทำงาน
  4. ถ้า consumer fail → message ยังอยู่ใน queue
  5. Consumer อื่นมารับทำแทนได้ (competing consumers)

Event-Driven Example with RabbitMQ

// ═══════════════════════════════
// Producer — Order Service
// ═══════════════════════════════
import amqp from 'amqplib';

class OrderService {
  private channel: amqp.Channel;

  async connect() {
    const conn = await amqp.connect('amqp://localhost');
    this.channel = await conn.createChannel();
    
    // Declare exchange (topic-based routing)
    await this.channel.assertExchange('events', 'topic', { durable: true });
  }

  async createOrder(userId: string, items: CartItem[]) {
    // 1. Business logic
    const order = await this.orderRepo.create({ userId, items });
    
    // 2. Publish event (async, non-blocking)
    const event = {
      type: 'order.created',
      data: {
        orderId: order.id,
        userId,
        items,
        total: order.total,
        timestamp: new Date().toISOString(),
      },
    };

    this.channel.publish(
      'events',
      'order.created',
      Buffer.from(JSON.stringify(event)),
      { persistent: true }  // Survives broker restart
    );

    return order;  // Return immediately!
  }
}

// ═══════════════════════════════
// Consumer — Email Service
// ═══════════════════════════════
class EmailConsumer {
  async start() {
    const conn = await amqp.connect('amqp://localhost');
    const channel = await conn.createChannel();

    await channel.assertExchange('events', 'topic', { durable: true });

    // Create queue for this consumer
    const q = await channel.assertQueue('email-service', { durable: true });
    
    // Bind to events we care about
    await channel.bindQueue(q.queue, 'events', 'order.created');
    await channel.bindQueue(q.queue, 'events', 'user.registered');

    // Process messages
    channel.prefetch(10);  // Process 10 at a time
    channel.consume(q.queue, async (msg) => {
      if (!msg) return;

      try {
        const event = JSON.parse(msg.content.toString());

        switch (event.type) {
          case 'order.created':
            await sendOrderConfirmation(event.data);
            break;
          case 'user.registered':
            await sendWelcomeEmail(event.data);
            break;
        }

        channel.ack(msg);  // ✅ Message processed
      } catch (err) {
        channel.nack(msg, false, true);  // ❌ Retry (requeue)
      }
    });
  }
}

Apache Kafka — High-Throughput Event Streaming

Apache Kafka แตกต่างจาก message queues ทั่วไป เพราะเป็น "event log" มากกว่า "message queue" หมายความว่า events จะถูกเก็บไว้ในระยะเวลาหนึ่ง (เช่น 7 วัน) และ consumer สามารถ replay events เหล่านั้นได้

Kafka เหมาะกับ use cases ที่ต้องการ high throughput (100K+ messages/second), event sourcing, log aggregation, หรือ real-time analytics ส่วน RabbitMQ เหมาะกับ task queues, RPC, หรือ notification systems ที่ไม่ต้อง replay

Apache Kafka
RabbitMQ = Message Queue
(messages ถูกลบหลัง consume)
Kafka = Event Log
(events ถูกเก็บถาวร, replay ได้)
Topic: orders
Partition 0: [e1][e2][e3][e4][e5]→
Partition 1: [e1][e2][e3]→
Partition 2: [e1][e2][e3][e4]→

Producers
(Order Service)
↓ ↓
Consumer Groups
├── Email Service
├── Analytics Service
└── Inventory Service
Key Features:
  • Retention: เก็บ events 7 วัน (configurable)
  • Replay: consumer ย้อนอ่านได้
  • Partitions: parallel processing
  • Consumer Groups: load balancing
  • Throughput: 100K+ messages/second
  • Ordering: per-partition ordering guarantee

RabbitMQ vs Kafka — Quick Comparison

RabbitMQ Apache Kafka
Model Message Queue Event Log
Messages Deleted after consumption Retained (7d+) replay possible
Throughput ~10K msg/sec ~100K+ msg/sec
Ordering Per-queue Per-partition
Routing ✅ Flexible (exchange types) Topic-based
Protocol AMQP Custom binary
Complexity ⭐⭐ Medium ⭐⭐⭐ Complex
Use Cases Task queues, notifications, RPC Event sourcing, analytics, logs, streaming
💡 Small/medium apps → RabbitMQ (simpler)
High-throughput, event sourcing → Kafka
Cloud-native → AWS SQS/SNS (simplest managed option)

🏗️ Communication Patterns in Distributed Systems

เมื่อระบบแยกออกเป็น multiple services แล้ว จะเกิดความท้าทายในเรื่องการสื่อสาร การ manage failures การ handle transactions ข้าม services และการ observe system behavior

Communication patterns เหล่านี้เป็น proven solutions ที่ช่วยแก้ปัญหาเหล่านั้น แต่ละ pattern มีจุดประสงค์และ use cases ที่แตกต่างกัน การเลือกใช้ต้องดูจากความต้องการและความซับซ้อนที่ยอมรับได้

1. API Gateway
Client → API Gateway → Services
Gateway handles: routing, auth, rate limiting
Tools: Kong, AWS API Gateway, Nginx
2. Service Mesh
Sidecar proxy per service (Envoy)
Handles: mTLS, load balancing, observability
Tools: Istio, Linkerd
3. Saga Pattern
Distributed transactions
Each step has a compensating action
Example: Order → Stock → Payment → Compensate if fail
4. CQRS
Command Query Responsibility Segregation
Write DB (normalized) ≠ Read DB (denormalized)
Sync via events between the two DBs
5. Circuit Breaker
Closed → Open → Half-Open → Closed
If Service B fails 5 times → stop calling
Prevents cascading failures

API Gateway เป็น single entry point ที่ handle routing, authentication, rate limiting, และ logging ช่วยลด complexity ฝั่ง client และ centralize cross-cutting concerns

Service Mesh เป็น infrastructure layer ที่ handle service-to-service communication ผ่าน sidecar proxy ให้ mTLS, load balancing, และ observability โดยไม่ต้องแก้ application code

Saga Pattern แก้ปัญหา distributed transactions โดยแบ่งเป็น steps และมี compensating action สำหรับแต่ละ step ถ้า step ใดล้มเหลว

CQRS แยก write model (normalized for consistency) กับ read model (denormalized for performance) เหมาะกับ systems ที่ read-heavy

Circuit Breaker ป้องกัน cascading failures โดยหยุดเรียก service ที่ล้มเหลว และค่อยๆ ลองใหม่เมื่อระบบฟื้นตัว


🌍 Real-World Architecture Examples

การดู architecture ตัวอย่างจากโลกจริงจะช่วยให้เข้าใจว่า patterns ต่างๆ ถูกนำไปใช้งานอย่างไรบ้าง และเหมาะกับ scale ขนาดไหน ที่นี่เราจะดู 2 แบบ: E-commerce ขนาดกลาง และ SaaS startup

E-commerce Platform (Medium Scale)

E-commerce Architecture (Medium Scale)
CDN (CloudFlare / CloudFront)
→ Static assets (images, CSS, JS)
Load Balancer (Nginx / ALB)
Web App (Next.js SSR)
Multiple instances
API Gateway (Kong)
→ Auth, rate limiting, logging
User Service
Product Service
Order Service
Payment Service
PostgreSQL + Redis + RabbitMQ + Elasticsearch

SaaS Application (Simpler — Modular Monolith)

SaaS App Architecture (Startup Stage)
Frontend (React/Next.js on Vercel)
API Server (Modular Monolith — Node.js)
Auth | Users | Billing | Teams | API Modules
PostgreSQL (single DB, schema per module)
Redis
(cache)
S3/R2
(files)
Background Jobs
(BullMQ)
💡 เริ่มง่าย, scale ได้, ค่อย extract ทีหลัง

E-commerce Architecture แสดงให้เห็น layered approach ที่มี CDN สำหรับ static assets, load balancer สำหรับ distribution, multiple web app instances สำหรับ scalability, API gateway สำหรับ API management, และ microservices แยกตาม domain

SaaS Architecture แสดงแนวทางสำหรับ startup ที่เลือกใช้ modular monolith เพื่อความ simple แต่ยังคง modularity ไว้สำหรับการแยกออกเป็น microservices ในอนาคต plus managed services เพื่อลด operational overhead


📋 Architecture Decision Cheat Sheet

การเลือก architecture ไม่มี "correct answer" ที่ universal เพราะทุกอย่างเป็น trade-offs ที่ต้องดูจากบริบทของ project, ทีม, timeline, budget, และ requirements แต่ guidelines เหล่านี้จะช่วยให้ตัดสินใจได้ง่ายขึ้น

📦 Architecture
Solo/small team (1-5)
→ Modular Monolith
Medium team (5-20)
→ Modular Monolith + Queue
Large team (20+)
→ Microservices
"I'm not sure"
→ Monolith (you can always split)
🌐 API Style
Public API
→ REST (everyone knows it)
Mobile/SPA with complex data
→ GraphQL
Service-to-service
→ gRPC (speed + type safety)
Real-time
→ WebSocket or Server-Sent Events
📨 Communication
Need immediate response
→ Sync (REST/gRPC)
Can delay processing
→ Async (Message Queue)
Need event replay
→ Kafka
Simple task queue
→ RabbitMQ or BullMQ
Managed + simple
→ AWS SQS/SNS
📐 Design Pattern
Small app
→ MVC (simple, proven)
Medium app
→ Layered (Controller-Service-Repo)
Complex domain
→ Clean/Hexagonal Architecture
"I'm overthinking"
→ MVC is fine, ship it!

🔑 Key Takeaways

หลังจากไปดู architecture patterns, API styles, design patterns, และ communication patterns ต่างๆ แล้ว มาสรุป lessons สำคัญที่จะช่วยให้เลือกใช้ได้ถูกต้องและไม่ over-engineer

  1. Start Monolith, Split Later → อย่าเริ่มด้วย microservices ถ้าไม่จำเป็น — modular monolith ดีพอสำหรับ 90% ของ startups
  2. MVC → Layered → Clean → เลือก complexity ให้เหมาะกับ project — MVC ก็ ship ได้!
  3. REST for public, GraphQL for SPA, gRPC for internal → ไม่ต้องเลือกอันเดียว ใช้ร่วมกันได้
  4. Event-driven = resilient → ใช้ message queues สำหรับงานที่ไม่ต้อง respond ทันที
  5. RabbitMQ vs Kafka → Task queue ใช้ RabbitMQ, event streaming ใช้ Kafka
  6. Saga ≠ distributed transaction → แต่ละ step มี compensating action
  7. CQRS → แยก write model กับ read model ถ้า read-heavy
  8. Circuit Breaker → ป้องกัน cascading failures ใน distributed systems
  9. API Gateway → Single entry point สำหรับ auth, rate limit, routing
  10. Architecture คือ trade-offs → ไม่มี "best" architecture — มีแค่ "right for this situation"
บทความจากซีรีส์ DevOps & Vibe Coding 2026
← Previous
Authentication & Authorization — ระบบยืนยันตัวตนและจัดการสิทธิ์อย่างปลอดภัย
Next →
Frontend Performance & Modern Frameworks — เพิ่มความเร็วและเลือก Framework ที่ใช่