Published on

Anemic Model vs Rich Model

Authors

Anemic Model vs Rich Model

ในสถาปัตยกรรมซอฟต์แวร์ การออกแบบโมเดลข้อมูลมี 2 แนวทางหลักที่พบบ่อย คือ Anemic Model และ Rich Model ทั้งสองแนวทางนี้มีข้อดี-ข้อเสียแตกต่างกันไป ขึ้นอยู่กับความซับซ้อนของระบบและหลักการออกแบบที่ต้องการใช้

ในบทความนี้ เราจะอธิบายความแตกต่างระหว่าง Anemic Model และ Rich Model พร้อมตัวอย่างโค้ดในภาษา Go โดยใช้ Use Case เดียวกันเพื่อเปรียบเทียบแนวทางทั้งสอง และนำเสนอ Best Practices สำหรับการเลือกใช้งานในแต่ละกรณี


1. ความหมายของ Anemic Model และ Rich Model

Anemic Model

เป็นรูปแบบที่เน้นการแยก Data ออกจาก Behavior (Business Logic) โดยโมเดลจะมีเพียง Field หรือ Property ที่ใช้เก็บข้อมูล และตรรกะทั้งหมดจะอยู่ใน Service Layer แทน

ข้อดี:

  • เข้าใจง่าย เหมาะกับระบบที่มีความซับซ้อนน้อย
  • แยก Business Logic ออกจาก Data ทำให้โครงสร้างชัดเจน

ข้อเสีย:

  • เสี่ยงต่อการละเมิด Encapsulation
  • อาจทำให้ Service Layer มีขนาดใหญ่และจัดการยาก
  • มีแนวโน้มเกิด Code Duplication เพราะ Business Logic ถูกกระจายไปหลาย Service

Rich Model

เป็นรูปแบบที่รวม Data และ Behavior ไว้ในโมเดลเดียวกัน โดยโมเดลมีหน้าที่รักษาสถานะ (State) ของตัวเอง และมีฟังก์ชันที่ทำงานกับข้อมูลโดยตรง

ข้อดี:

  • รักษา Encapsulation ได้ดี
  • ลดการทำซ้ำของ Business Logic
  • โค้ดอ่านง่ายขึ้น เพราะตรรกะอยู่ใกล้กับข้อมูล

ข้อเสีย:

  • มีความซับซ้อนมากขึ้นหากโมเดลขยายใหญ่
  • หากไม่จัดการดี อาจเกิดการผูกมัด (Coupling) ระหว่างโมดูล

2. Use Case: การจัดการยอดคงเหลือในบัญชี (Bank Account Management)

โจทย์:

  1. ผู้ใช้สามารถเปิดบัญชีใหม่ได้
  2. สามารถฝากเงินและถอนเงินได้
  3. ยอดเงินในบัญชีต้องไม่ติดลบ

3. ตัวอย่างการใช้งาน Anemic Model

โครงสร้าง

  • Account (Data Model)
  • AccountService (Business Logic)
// Account: Data Model (Anemic Model)
type Account struct {
    ID      string
    Owner   string
    Balance float64
}

// AccountService: Business Logic
type AccountService struct{}

func (s *AccountService) Deposit(account *Account, amount float64) error {
    if amount <= 0 {
        return fmt.Errorf("amount must be greater than zero")
    }
    account.Balance += amount
    return nil
}

func (s *AccountService) Withdraw(account *Account, amount float64) error {
    if amount <= 0 {
        return fmt.Errorf("amount must be greater than zero")
    }
    if account.Balance < amount {
        return fmt.Errorf("insufficient balance")
    }
    account.Balance -= amount
    return nil
}

func main() {
    account := &Account{
        ID:    "12345",
        Owner: "John Doe",
    }

    service := &AccountService{}

    // ฝากเงิน
    if err := service.Deposit(account, 1000); err != nil {
        log.Fatal(err)
    }

    // ถอนเงิน
    if err := service.Withdraw(account, 500); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Account balance: %.2f\n", account.Balance)
}

ข้อสังเกต:

  • Business Logic ทั้งหมดอยู่ใน AccountService
  • Account เป็นเพียงโครงสร้างข้อมูล ไม่มีพฤติกรรมใด ๆ
  • ทุกครั้งที่มีการเปลี่ยนแปลงข้อมูล ต้องผ่าน Service

4. ตัวอย่างการใช้งาน Rich Model

โครงสร้าง

  • Account (Rich Model)
// Account: Rich Model (Data + Behavior)
type Account struct {
    id      string
    owner   string
    balance float64
}

// สร้าง Account ใหม่ (Factory Method)
func NewAccount(id, owner string) *Account {
    return &Account{
        id:    id,
        owner: owner,
    }
}

// ฝากเงิน
func (a *Account) Deposit(amount float64) error {
    if amount <= 0 {
        return fmt.Errorf("amount must be greater than zero")
    }
    a.balance += amount
    return nil
}

// ถอนเงิน
func (a *Account) Withdraw(amount float64) error {
    if amount <= 0 {
        return fmt.Errorf("amount must be greater than zero")
    }
    if a.balance < amount {
        return fmt.Errorf("insufficient balance")
    }
    a.balance -= amount
    return nil
}

// ดึงยอดคงเหลือ
func (a *Account) Balance() float64 {
    return a.balance
}

func main() {
    account := NewAccount("12345", "John Doe")

    if err := account.Deposit(1000); err != nil {
        log.Fatal(err)
    }

    if err := account.Withdraw(500); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Account balance: %.2f\n", account.Balance())
}

ข้อสังเกต:

  • ตรรกะทั้งหมดอยู่ภายใน Account
  • ควบคุมการเข้าถึง Field ผ่าน Method ต่าง ๆ (Encapsulation)
  • ลดการทำซ้ำของ Business Logic

5. เมื่อไหร่ควรใช้ Anemic Model vs Rich Model?

เกณฑ์Anemic ModelRich Model
ความซับซ้อน (Complexity)เหมาะกับระบบที่เรียบง่ายเหมาะกับระบบที่ซับซ้อน
การจัดการ Business Logicอยู่ใน Service Layerอยู่ในตัวโมเดลเอง
การรักษาสถานะ (State)บริหารโดย Service Layerบริหารโดยตัวโมเดล
การทดสอบ (Testability)แยกการทดสอบง่ายต้อง Mock โมเดลหากซับซ้อน
การขยายระบบ (Scalability)ขยายง่ายกว่าเมื่อแยก Serviceยากขึ้นถ้าโมเดลมีขนาดใหญ่

6. Best Practices ในการเลือกใช้งาน

  1. ใช้ Anemic Model เมื่อ:
    • ระบบมี Business Logic น้อย
    • ต้องการแยก Data และ Behavior ออกจากกันเพื่อความชัดเจน
    • ต้องการลด Coupling ระหว่างโมดูล
  2. ใช้ Rich Model เมื่อ:
    • ระบบมี Business Logic ซับซ้อนและเฉพาะเจาะจง
    • ต้องการให้โมเดลจัดการ State ของตัวเอง
    • เน้น Encapsulation เพื่อป้องกันข้อมูลไม่ให้ถูกแก้ไขโดยตรง

สรุป

  • Anemic Model เหมาะสำหรับระบบที่มีความซับซ้อนต่ำและต้องการแยก Business Logic ออกจากโมเดล
  • Rich Model เหมาะสำหรับระบบที่ต้องการรวมข้อมูลและพฤติกรรมเข้าด้วยกันเพื่อให้โมเดลควบคุมการเปลี่ยนแปลงของตัวเอง

ทั้งสองแนวทางมีข้อดี-ข้อเสีย การเลือกใช้งานขึ้นอยู่กับลักษณะของระบบและความต้องการทางธุรกิจเป็นสำคัญ!