🐕 ทำไมต้องรู้ Architecture?
Architecture เป็นหัวใจสำคัญของการพัฒนาซอฟต์แวร์ที่หลายคนมองข้าม เพราะมันไม่ใช่แค่เรื่องของการเขียนโค้ดให้ทำงานได้ แต่เป็นการออกแบบโครงสร้างที่จะทำให้ระบบของเราสามารถ scale ได้ ดูแลรักษาได้ และพัฒนาต่อยอดได้ในระยะยาว
ความแตกต่างระหว่างโค้ดที่ดีกับ architecture ที่ดีนั้นเปรียบเสมือนความแตกต่างระหว่างการสร้างบ้านที่สวยงามกับการวางแปลนบ้านที่ดี บ้านที่สวยแต่แปลนแย่อาจพังได้ง่าย แต่บ้านที่แปลนดีแม้จะตกแต่งไม่สวยก็ยังแก้ไขและปรับปรุงได้
- 1 ไฟล์ 10,000 บรรทัด
- แก้จุดนึง → พังอีก 5 จุด
- Deploy ทีนึง → ลุ้นทั้งทีม
- Scale ไม่ได้ → server ล่มตอน peak
- Developer ใหม่ต้อง 3 เดือนกว่าจะเข้าใจ
- แต่ละส่วนแยกชัดเจน
- แก้จุดนึง → ไม่กระทบจุดอื่น
- 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 เร็วๆ
- Deploy ทีเดียว ทั้งก้อน
- Scale ทีเดียว ทั้งก้อน
- พังทีเดียว... ก็ทั้งก้อน 💀
- แต่ละ 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 แล้วค่อยแยก"
(NOT direct DB access)
(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
📐 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 กลับไป)
(Template)
HTML/EJS
(Logic)
Routes + Handlers
(Data + Business Logic)
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
→ HTTP requests, validation, response format
→ Business rules, orchestration, validation
→ Database queries, ORM, external APIs
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
Express, PostgreSQL, Redis, AWS S3
Controllers, Presenters, Gateways
Entities
Domain Models
- dependencies point INWARD only
- Inner layers know NOTHING about outer layers
- Use interfaces/ports at boundaries
🌐 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 แยกกัน
- 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
Client บอกว่าต้องการ data อะไร
user(id: 123) {
name
avatar
posts(limit: 5) {
title
likes
}
}
}
"data": {
"user": {
"name": "Alice",
"avatar": "url...",
"posts": [...]
}
}
}
# ═══════════════════════════════
# 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
- Binary protocol (Protocol Buffers) → 10x faster
- Strongly typed → contract-first development
- Streaming support → real-time data
- Code generation → auto-generate client/server
(Node.js)
(binary)
(Go)
(text, slow)
// ═══════════════════════════════
// 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 นั้น
⚠️ ถ้า email server ล่ม → ทั้ง order fail!
├── Send email .............. async
├── Send SMS ................ async
├── Update analytics ........ async
└── Generate invoice ........ async
✅ 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
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
(messages ถูกลบหลัง consume)
(events ถูกเก็บถาวร, replay ได้)
Partition 1: [e1][e2][e3]→
Partition 2: [e1][e2][e3][e4]→
Producers
(Order Service)
Consumer Groups
├── Email Service
├── Analytics Service
└── Inventory Service
- 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 ที่แตกต่างกัน การเลือกใช้ต้องดูจากความต้องการและความซับซ้อนที่ยอมรับได้
Gateway handles: routing, auth, rate limiting
Handles: mTLS, load balancing, observability
Each step has a compensating action
Write DB (normalized) ≠ Read DB (denormalized)
If Service B fails 5 times → stop calling
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)
→ Static assets (images, CSS, JS)
Multiple instances
→ Auth, rate limiting, logging
SaaS Application (Simpler — Modular Monolith)
Auth | Users | Billing | Teams | API Modules
(cache)
(files)
(BullMQ)
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 เหล่านี้จะช่วยให้ตัดสินใจได้ง่ายขึ้น
🔑 Key Takeaways
หลังจากไปดู architecture patterns, API styles, design patterns, และ communication patterns ต่างๆ แล้ว มาสรุป lessons สำคัญที่จะช่วยให้เลือกใช้ได้ถูกต้องและไม่ over-engineer
- Start Monolith, Split Later → อย่าเริ่มด้วย microservices ถ้าไม่จำเป็น — modular monolith ดีพอสำหรับ 90% ของ startups
- MVC → Layered → Clean → เลือก complexity ให้เหมาะกับ project — MVC ก็ ship ได้!
- REST for public, GraphQL for SPA, gRPC for internal → ไม่ต้องเลือกอันเดียว ใช้ร่วมกันได้
- Event-driven = resilient → ใช้ message queues สำหรับงานที่ไม่ต้อง respond ทันที
- RabbitMQ vs Kafka → Task queue ใช้ RabbitMQ, event streaming ใช้ Kafka
- Saga ≠ distributed transaction → แต่ละ step มี compensating action
- CQRS → แยก write model กับ read model ถ้า read-heavy
- Circuit Breaker → ป้องกัน cascading failures ใน distributed systems
- API Gateway → Single entry point สำหรับ auth, rate limit, routing
- Architecture คือ trade-offs → ไม่มี "best" architecture — มีแค่ "right for this situation"