Design Patterns
Back to Software Development Index
Domain-Driven Design (DDD)
Domain-driven design (DDD) เป็นแนวคิดในการพัฒนา software ที่เน้นให้ความสำคัญกับ domain (ธุรกิจหรือโจทย์ที่ต้องแก้) เป็นหลัก โดยให้ทุกคนในทีมมีความเข้าใจตรงกันในภาษา domain นั้นๆ ผ่านกระบวนการสื่อสารและทำงานร่วมกัน
Core Concepts
1. Domain Types
DDD แบ่ง domain ออกเป็น 3 ประเภท:
Core Domain
- เป็นธุรกิจหลัก (core business) ของบริษัท
- จุดเด่นที่ทำให้แตกต่างจากคู่แข่ง
- ต้องการ developers ที่เก่งที่สุด
- Example: สำหรับ e-commerce → recommendation engine, pricing algorithms
Supporting Subdomain
- สนับสนุนการทำงานของ core domain
- สร้างแยกได้ หรือจ้างบุคคลภายนอก (third-party)
- มีความซับซ้อนปานกลาง
- Example: Inventory management, shipping logistics
Generic Subdomain
- เป็นงานทั่วไปที่ไม่ได้มี business value มาก
- มักใช้ solution สำเร็จจากภายนอก
- Example: Authentication, logging, email services
2. Strategic Design
Ubiquitous Language
- ภาษาที่ทุกคนในทีมใช้ร่วมกัน (developer, business stakeholders)
- ต้องสอดคล้องกับ code, documentation, และการสื่อสาร
Bounded Context
- กำหนดขอบเขตของ model แต่ละส่วน
- แต่ละ context มี model และภาษาเป็นของตัวเอง
- Example: “Customer” ใน Sales Context vs “User” ใน Support Context
Context Mapping
- กำหนดความสัมพันธ์ระหว่าง bounded contexts
- Patterns: Shared Kernel, Customer-Supplier, Conformist, Anticorruption Layer
3. Tactical Design
Entities
- Objects ที่มี identity ที่ไม่ซ้ำ
- มี lifecycle
- Example: User, Order, Product
Value Objects
- Objects ที่ไม่มี identity
- เปรียบเทียบด้วยค่าที่เก็บ (immutable)
- Example: Address, Money, DateRange
Aggregates
- กลุ่มของ entities และ value objects
- มี Aggregate Root เป็นตัวควบคุม
- รักษา business invariants
Repositories
- จัดการการเก็บและดึง aggregates
- แยก domain logic จาก infrastructure
Domain Events
- เหตุการณ์สำคัญที่เกิดขึ้นใน domain
- ใช้สำหรับการสื่อสารระหว่าง bounded contexts
Services
- Operations ที่ไม่เหมาะกับ entities หรือ value objects
- Stateless operations
Event Storming
เป็นเทคนิคการ workshop ที่ทีมมารวมตัวกันเพื่อทำความเข้าใจ domain
Process:
- ระบุ Domain Events (สิ่งที่เกิดขึ้น)
- หา Commands (สิ่งที่ trigger events)
- กำหนด Aggregates (ที่จัดการ commands)
- แบ่ง Bounded Contexts
Benefits:
- สร้าง shared understanding
- ค้นพบ domain complexity
- ระบุ bounded contexts
- เข้าใจ business process
DDD Implementation Guidelines
When to Use DDD ✅
- Complex business domains
- Long-term projects
- Need for close collaboration with domain experts
- Multiple teams working on different areas
When NOT to Use DDD ❌
- Simple CRUD applications
- Technical/infrastructure problems
- Tight deadlines with simple requirements
- Small projects
DDD vs Clean Architecture
| Aspect | DDD | Clean Architecture |
|---|---|---|
| Focus | Domain modeling | Dependency management |
| Core | Bounded contexts | Business rules isolation |
| Approach | Model-driven | Layer-driven |
| Complexity | High (model complexity) | Medium (structural) |
| Best For | Complex domains | Any application size |
They Complement Each Other:
- DDD helps model the domain
- Clean Architecture helps structure the code
- Use DDD for entities/aggregates layer
- Use Clean Architecture for overall structure
Factory Pattern
Factory Pattern เป็น design pattern ที่ช่วยในการสร้าง object โดยไม่ต้องระบุ class ที่แน่นอนที่จะสร้าง
Benefits
- Encapsulation - ซ่อนการสร้าง object ไว้ใน factory
- Flexibility - ง่ายต่อการเพิ่ม product types ใหม่
- Single Responsibility - แยก creation logic ออกจาก business logic
- Dependency Inversion - Client ไม่ต้องรู้จัก concrete classes
Go Implementation
package main
import "fmt"
// Product Interface
type IWeapon interface {
GetName() string
GetPower() int
}
// Concrete Product 1: Sword
type Sword struct {
name string
power int
}
func (s Sword) GetName() string {
return s.name
}
func (s Sword) GetPower() int {
return s.power
}
// Concrete Product 2: Bow
type Bow struct {
name string
power int
}
func (b Bow) GetName() string {
return b.name
}
func (b Bow) GetPower() int {
return b.power
}
// Factory
type WeaponFactory struct{}
func (wf WeaponFactory) CreateWeapon(weaponType string) IWeapon {
switch weaponType {
case "sword":
return Sword{
name: "Steel Sword",
power: 50,
}
case "bow":
return Bow{
name: "Wooden Bow",
power: 35,
}
default:
return nil
}
}
// Client Code
func main() {
factory := WeaponFactory{}
// Create sword
sword := factory.CreateWeapon("sword")
fmt.Printf("Created: %s with power %d\n",
sword.GetName(), sword.GetPower())
// Create bow
bow := factory.CreateWeapon("bow")
fmt.Printf("Created: %s with power %d\n",
bow.GetName(), bow.GetPower())
}Output:
Created: Steel Sword with power 50
Created: Wooden Bow with power 35
Factory Pattern Variations
1. Simple Factory
- Single factory method
- Returns different types based on parameter
- ตัวอย่างข้างบนคือ Simple Factory
2. Factory Method
- Abstract factory method in base class
- Subclasses implement to create specific products
- Follows Open/Closed Principle
3. Abstract Factory
- Factory of factories
- Creates families of related objects
- Multiple factory methods
When to Use Factory Pattern
Use When:
- Object creation is complex
- Need to return different types based on context
- Want to hide creation logic
- Following dependency inversion
Avoid When:
- Simple object creation (use
newdirectly) - No variation in creation logic
- Over-engineering simple problems
Related:
References: