Blog

  • ตอนที่ 8: Repository ใน Domain-Driven Design – ทำไมไม่ใช่แค่ CRUD

    ตอนที่ 8: Repository ใน Domain-Driven Design – ทำไมไม่ใช่แค่ CRUD

    บทนำ

    เมื่อพูดถึงคำว่า Repository นักพัฒนาจำนวนมากมักจะนึกถึงไฟล์ที่มีแต่ create, read, update, delete

    แต่ใน Domain-Driven Design (DDD) นั้น Repository มีความหมายและบทบาทที่ลึกกว่านั้นมาก

    บทความตอนนี้จะอธิบายว่า Repository ใน DDD คืออะไร, แตกต่างจาก CRUD แบบทั่วไปอย่างไร และควรออกแบบอย่างไรให้ Domain สะอาดและยั่งยืน โดยใช้ตัวอย่าง ระบบพนักงาน (Employee Management System)


    Repository ใน Domain-Driven Design คืออะไร

    Repository คือ abstraction ที่ทำหน้าที่:

    • ทำให้ Domain รู้สึกเหมือนกำลังทำงานกับ object ในหน่วยความจำ
    • ซ่อนรายละเอียดของ database, ORM, API หรือ storage ใด ๆ
    • ให้ Domain Model โฟกัสกับ business logic โดยไม่ผูกกับเทคโนโลยี

    Repository คือ “ภาพลวงตา” ที่ทำให้ Domain คิดว่า object ยังมีชีวิตอยู่ตลอดเวลา


    Repository ไม่ใช่ DAO และไม่ใช่ CRUD

    ความเข้าใจผิดที่พบบ่อยคือ:

    Repository = DAO + CRUD

    ใน DDD แนวคิดนี้ ผิดโดยหลักการ

    เปรียบเทียบ Repository vs CRUD

    หัวข้อRepository (DDD)CRUD / DAO
    มุมมองDomain-centricDatabase-centric
    คืนค่าAggregateRecord / Row
    เมธอดใช้ภาษา Domainใช้ภาษาเทคนิค
    ผู้ใช้งานDomain / Application ServiceController / Service

    Repository ถูกออกแบบมาเพื่อ Aggregate ไม่ใช่ table


    ตัวอย่างจากระบบพนักงาน

    Aggregate: Employee

    สิ่งที่ Repository ควรมี:

    • findById(EmployeeId id)
    • save(Employee employee)
    • findActiveEmployees()

    สิ่งที่ Repository ไม่ควรมี:

    • updateSalaryById()
    • deleteByDepartmentId()
    • findBySalaryGreaterThan() (ถ้าไม่ใช่ภาษา Domain)

    ถ้า method ไหนอ่านแล้วไม่ใช่ภาษาของธุรกิจ นั่นคือสัญญาณเตือน


    Repository ทำงานร่วมกับ Application Service อย่างไร

    Repository ไม่ควรถูกเรียกโดย UI หรือ Controller โดยตรง

    โครงสร้างที่เหมาะสมคือ:

    UI / API
       ↓
    Application Service / Use Case
       ↓
    Repository
       ↓
    Domain Model (Aggregate)

    Application Service:

    • ตัดสินใจว่า use case ต้องโหลดอะไร
    • เรียก Repository
    • ส่ง Aggregate ให้ Domain ทำงานต่อ

    ทำไม Repository ควรคืนค่าเป็น Aggregate

    เพราะ:

    • Aggregate คือ boundary ของ consistency
    • Business rule ถูกคุมอยู่ภายใน
    • ป้องกันการแก้ state แบบข้ามกฎ

    ถ้า Repository คืนค่าเป็น DTO หรือ Map:

    • Domain จะอ่อนแรง
    • Invariant จะถูกละเมิดง่าย
    • Logic จะไหลไปอยู่ผิดที่

    Repository กับ Infrastructure

    ใน DDD:

    • Interface ของ Repository อยู่ใน Domain Layer
    • Implementation อยู่ใน Infrastructure Layer

    Domain:

    • ไม่รู้จัก database
    • ไม่รู้จัก ORM
    • ไม่รู้จัก framework

    Infrastructure:

    • แปลง Aggregate ↔ persistence model
    • จัดการ transaction ระดับเทคนิค

    สัญญาณว่า Repository ของคุณเริ่มผิดทาง

    • Repository มี method เยอะผิดปกติ
    • Method name เต็มไปด้วยเงื่อนไขเชิง query
    • Business logic เริ่มไหลเข้า Repository
    • Aggregate กลายเป็น data holder

    ถ้าเห็นสัญญาณเหล่านี้ แปลว่าคุณกำลังหลุดจาก DDD


    สรุป

    • Repository ใน Domain-Driven Design ไม่ใช่ CRUD
    • ถูกออกแบบเพื่อ Aggregate ไม่ใช่ table
    • ใช้ภาษา Domain ไม่ใช่ภาษา database
    • ช่วยปกป้อง business rule และโครงสร้างของ Domain

    ในตอนถัดไป เราจะพูดถึง Domain Event – เมื่อระบบธุรกิจขับเคลื่อนด้วยเหตุการณ์ ซึ่งเป็นก้าวสำคัญของระบบที่ scale ได้จริง

  • ตอนที่ 7: Application Service / Use Case – ตัวกลางระหว่างโลกธุรกิจกับโลกเทคนิค

    ตอนที่ 7: Application Service / Use Case – ตัวกลางระหว่างโลกธุรกิจกับโลกเทคนิค

    บทนำ

    หลังจากตอนที่ 6 เราได้รู้จัก Domain Service ซึ่งเป็นที่อยู่ของ business logic ที่ไม่ควรผูกกับ Entity ใดโดยตรง

    คำถามถัดมาที่มักจะตามมาคือ:

    แล้วโค้ดที่รับ request จาก UI, เรียก Domain Service, ใช้ Repository และจัดการ transaction ควรอยู่ที่ไหน?

    คำตอบคือ Application Service (หรือที่หลายทีมเรียกว่า Use Case)


    Application Service คืออะไร

    Application Service คือชั้นที่ทำหน้าที่:

    • รับคำสั่งจากภายนอก (UI, API, Controller)
    • ประสานงานระหว่าง Domain Objects
    • ควบคุม flow ของ use case หนึ่ง ๆ
    • จัดการ transaction, permission, orchestration

    ❗ Application Service ไม่ใช่ ที่อยู่ของ business rule เชิงลึก

    มันคือ “ผู้จัดการกระบวนการ” ไม่ใช่ “ผู้ตัดสินกฎธุรกิจ”


    เปรียบเทียบง่าย ๆ ด้วยระบบพนักงาน

    สมมติ use case: ปรับเงินเดือนพนักงาน

    สิ่งที่ Application Service ทำ:

    1. รับคำสั่งปรับเงินเดือนจาก UI
    2. โหลด Employee Aggregate จาก Repository
    3. เรียก Domain Service เพื่อตรวจสอบกฎธุรกิจ
    4. สั่งให้ Aggregate เปลี่ยนสถานะ
    5. บันทึกผลลัพธ์กลับ Repository

    สิ่งที่ Application Service ไม่ควรทำ:

    • คำนวณสูตรเงินเดือนเอง
    • ตรวจสอบ policy เชิงลึกของบริษัท
    • เขียน if/else ธุรกิจยาว ๆ

    ตัวอย่างโครงสร้าง (เชิงแนวคิด)

    UI / API
       ↓
    Application Service (Use Case)
       ↓
    Domain Model (Entity, Value Object, Domain Service)
       ↓
    Repository

    Application Service คือสะพานเชื่อมโลกภายนอกกับ Domain


    ตัวอย่างเชิงโค้ด (pseudo code)

    class AdjustSalaryUseCase {
      execute(command) {
        employee = employeeRepository.getById(command.employeeId)
    
        salaryPolicyService.validate(employee, command.newSalary)
    
        employee.adjustSalary(command.newSalary)
    
        employeeRepository.save(employee)
      }
    }

    สังเกตว่า:

    • ไม่มี logic ธุรกิจซับซ้อนอยู่ใน Use Case
    • Business rule อยู่ใน Domain
    • Use Case ทำหน้าที่เรียงลำดับขั้นตอนเท่านั้น

    Application Service vs Domain Service

    หัวข้อApplication ServiceDomain Service
    โฟกัสFlow / Use caseBusiness rule
    รู้จัก UIรู้ไม่รู้
    รู้จัก Persistenceรู้ไม่รู้
    Statelessส่วนใหญ่ใช่ใช่

    ถ้า logic นั้นเปลี่ยนเพราะ ขั้นตอนทำงานเปลี่ยน → Application Service

    ถ้า logic นั้นเปลี่ยนเพราะ กฎธุรกิจเปลี่ยน → Domain Service


    ทำไม Application Service ถึงสำคัญมาก

    ถ้าไม่มี Application Service:

    • Controller จะอ้วน
    • Business logic กระจัดกระจาย
    • Test ยาก
    • Domain ถูกใช้ผิดวิธี

    Application Service ช่วยให้:

    • Use case ชัดเจน
    • อ่านโค้ดแล้วรู้ว่า “ระบบทำอะไรได้บ้าง”
    • Domain สะอาดและคงทนต่อการเปลี่ยนแปลง

    สรุป

    • Application Service คือ ผู้ควบคุม flow ของระบบ
    • ไม่ใช่ที่อยู่ของ business rule
    • เป็นสะพานเชื่อม UI กับ Domain
    • ทำให้ระบบ scale ทางความซับซ้อนได้โดยไม่เละ

    ตอนนี้ Domain ของคุณเริ่มแข็งแรงแล้ว

    ตอนถัดไป เราจะลงลึกเรื่อง Repository – มุมมองแบบ DDD vs CRUD ซึ่งเป็นอีกจุดที่หลายทีมเข้าใจผิดมาก

  • Domain Service – เมื่อ logic ไม่ควรอยู่ใน Entity

    Domain Service – เมื่อ logic ไม่ควรอยู่ใน Entity

    ตอนที่ 6 ของซีรีส์ Domain-Driven Design ฉบับระบบธุรกิจจริง

    Theme หลัก: ระบบบริหารพนักงาน (Staff / Employee Management System)

    ในตอนที่ 5 เราใช้ Aggregate เพื่อกำหนดขอบเขตและปกป้อง invariant ของ domain

    แต่เมื่อเริ่มออกแบบจริง คุณจะเจอสถานการณ์แบบนี้:

    logic นี้เป็นกฎของธุรกิจแน่ ๆ … แต่ไม่รู้จะใส่ไว้ใน entity ตัวไหนดี

    นี่คือจุดที่ Domain Service เข้ามามีบทบาท


    ปัญหาที่เจอบ่อยในระบบพนักงาน

    ลองดู use case ต่อไปนี้:

    • พนักงานขอเปลี่ยนกะ
    • ระบบต้องตรวจสอบว่า
      • ไม่ชนกับกะอื่นของพนักงาน
      • ไม่ชนกับกะที่ได้รับอนุมัติแล้ว
      • ไม่เกินนโยบายที่ HR กำหนด

    คำถามคือ:

    • logic นี้ควรอยู่ใน ShiftRequest?
    • หรืออยู่ใน Staff?

    คำตอบคือ: ไม่ชัดเจนทั้งคู่


    ความเข้าใจผิดเกี่ยวกับ Service

    ในระบบทั่วไป เรามักเห็น:

    • ShiftService
    • StaffService
    • RequestService

    ที่ภายในเต็มไปด้วย if-else

    DDD แยก Service ออกเป็น 3 ประเภท:

    1. Application Service (Use Case)
    2. Domain Service
    3. Infrastructure Service

    ตอนนี้เราจะโฟกัสที่ข้อ 2


    Domain Service คืออะไร

    Domain Service คือ:

    class ที่รวม logic ทางธุรกิจ

    ซึ่ง ไม่เป็นเจ้าของข้อมูลเอง

    และ ไม่เหมาะจะอยู่ใน entity ใด entity หนึ่ง

    คุณสมบัติสำคัญ:

    • อยู่ใน layer ของ domain
    • ใช้ภาษาเดียวกับธุรกิจ (Ubiquitous Language)
    • ไม่มี state ระยะยาว

    ตัวอย่าง Domain Service: ShiftPolicy

    class ShiftPolicy {
      bool canRequestShift(
        Staff staff,
        WorkingPeriod period,
        List<ShiftRequest> existingRequests,
      ) {
        if (staff.status != StaffStatus.active) return false;
    
        for (final request in existingRequests) {
          if (request.period.overlaps(period)) {
            return false;
          }
        }
    
        return true;
      }
    }

    จุดสำคัญ:

    • ไม่เก็บข้อมูลเอง
    • รับทุกอย่างผ่าน parameter
    • สื่อความหมายเชิงธุรกิจ

    Domain Service ไม่ใช่ Utility

    ข้อผิดพลาดที่พบบ่อยคือ:

    class DateUtils {
      static bool overlap(DateTime a, DateTime b) {}
    }

    นี่คือ Technical Utility

    แต่ Domain Service:

    • ต้องสะท้อนกฎของธุรกิจ
    • ชื่อบอก intent ชัดเจน

    ShiftPolicy ดีกว่า ShiftHelper


    Domain Service ทำงานร่วมกับ Aggregate

    Aggregate Root ยังคงเป็นผู้ตัดสินขั้นสุดท้าย

    ตัวอย่างใน ShiftRequest:

    class ShiftRequest {
      void request(
        Staff staff,
        WorkingPeriod period,
        ShiftPolicy policy,
        List<ShiftRequest> existing,
      ) {
        if (!policy.canRequestShift(staff, period, existing)) {
          throw Exception('Shift request not allowed');
        }
    
        // สร้าง request
      }
    }

    Policy ช่วยตัดสิน แต่ aggregate รับผิดชอบ invariant


    สัญญาณว่าคุณควรใช้ Domain Service

    คุณควรพิจารณา Domain Service เมื่อ:

    • logic ใช้หลาย entity
    • logic ไม่ belong กับ entity เดียว
    • การยัด logic ลง entity ทำให้มันอ้วนเกินไป

    แต่ถ้า logic เป็นพฤติกรรมของ entity โดยตรง:

    อย่ารีบใช้ Domain Service


    สิ่งที่ Domain Service ไม่ควรทำ

    • เรียก database
    • รู้จัก framework
    • มี side effect ภายนอก

    Domain Service ควร:

    • pure
    • test ง่าย
    • ย้ายได้โดยไม่กระทบ infrastructure

    สรุปตอนที่ 6

    • Domain Service = logic ธุรกิจที่ไม่มีตัวตน
    • ใช้เมื่อ entity ไม่เหมาะจะรับภาระนั้น
    • อย่าใช้แทน entity แบบพร่ำเพรื่อ

    ถ้า service ของคุณรู้จัก database

    นั่นไม่ใช่ Domain Service


    ตอนต่อไป

    ตอนที่ 7: Application Service / Use Case – หัวใจของการไหลของระบบ

    เราจะประกอบ domain ทั้งหมดเข้าด้วยกัน และดูว่า controller ควรบางแค่ไหน

  • Aggregate & Aggregate Root – กำแพงป้องกัน Domain ไม่ให้เละ

    Aggregate & Aggregate Root – กำแพงป้องกัน Domain ไม่ให้เละ

    ตอนที่ 5 ของซีรีส์ Domain-Driven Design ฉบับระบบธุรกิจจริง
    Theme หลัก: ระบบบริหารพนักงาน (Staff / Employee Management System)

    หลังจากตอนที่ 4 เราแยก Entity และ Value Object ได้แล้ว

    ขั้นต่อไปที่สำคัญไม่แพ้กันคือคำถามนี้:

    ใครมีสิทธิ์แก้ใครได้บ้าง?

    ถ้าไม่กำหนดขอบเขตให้ชัด Domain จะเริ่ม “เละ” แบบไม่รู้ตัว

    DDD ใช้แนวคิดนี้เพื่อแก้ปัญหา: Aggregate


    ปัญหาที่เจอบ่อยในระบบพนักงาน

    ลองดูสถานการณ์คุ้น ๆ:

    • หน้าจอหนึ่งแก้ ShiftRequest.status
    • อีกหน้าหนึ่งแก้ approvedBy
    • Batch job มาแก้ approvedAt

    ทุกอย่างเขียนถูก type

    แต่สุดท้ายเกิดคำถามว่า:

    • กฎยังครบไหม?
    • ใครเป็นคนรับผิดชอบ?
    • invariant ถูกทำลายไปหรือเปล่า?

    นี่คือสัญญาณว่า ยังไม่มี Aggregate


    Aggregate คืออะไร

    Aggregate คือ:

    กลุ่มของ Entity และ Value Object ที่ต้องถูกดูแลเป็นหน่วยเดียวกัน

    โดยมี Aggregate Root เป็นประตูทางเข้าเพียงทางเดียว

    หลักการสำคัญ:

    • แก้ข้อมูลภายใน aggregate ได้ ผ่าน root เท่านั้น
    • Root เป็นผู้ปกป้องกฎ (invariants)

    Aggregate Root คืออะไร

    Aggregate Root คือ Entity ตัวหลักที่:

    • มี identity
    • เป็นจุดเดียวที่ภายนอกเรียกใช้ได้
    • รับผิดชอบความถูกต้องของทั้ง aggregate

    ในระบบพนักงาน ตัวอย่าง Aggregate Root:

    • ShiftRequest
    • Staff

    ตัวอย่าง Aggregate: ShiftRequest

    มาดูโครงสร้างแบบง่าย:

    class ShiftRequest {
      final ShiftRequestId id;
      ShiftRequestStatus status;
      StaffId staffId;
      WorkingPeriod period;
      Approval approval;
    
      void approve(Staff approver) {
        if (status != ShiftRequestStatus.pending) {
          throw Exception('Only pending request can be approved');
        }
    
        approval = Approval.by(approver.id);
        status = ShiftRequestStatus.approved;
      }
    }
    

    Approval อาจเป็น Value Object ภายใน aggregate

    ภายนอก ไม่ควร ไปแก้ approval ตรง ๆ


    Invariant: กฎที่ห้ามถูกละเมิด

    Invariant คือกฎที่ต้องเป็นจริง เสมอ ภายใน aggregate

    ตัวอย่าง invariant ของ ShiftRequest:

    • request ที่ approved แล้ว ห้ามกลับเป็น pending
    • request หนึ่งรายการ อนุมัติได้เพียงครั้งเดียว
    • approver ต้องมีสิทธิ์ตามกฎธุรกิจ

    กฎเหล่านี้:

    ต้องถูกบังคับโดย Aggregate Root

    ไม่ใช่ controller หรือ service


    ทำไม “ห้ามข้าม Aggregate”

    ข้อผิดพลาดที่พบบ่อย:

    request.approval.approvedBy = approverId; // ❌
    

    โค้ดนี้:

    • ข้าม root
    • bypass กฎ
    • ทำลาย invariant

    ถ้าจำเป็นต้องเปลี่ยน:

    request.approve(approver); // ✅
    

    ขนาดของ Aggregate สำคัญมาก

    Aggregate ที่ดีควร:

    • เล็กที่สุดเท่าที่จำเป็น
    • ไม่ดึงทุกอย่างมารวมกัน

    ตัวอย่างที่ควรแยก:

    • Staff ไม่ควรเป็น aggregate เดียวกับ ShiftRequest
    • ใช้ StaffId อ้างอิงแทน object เต็ม

    เหตุผล:

    • performance
    • concurrency
    • ลด coupling

    Repository ทำงานกับ Aggregate

    Repository ควร:

    • load / save ทั้ง aggregate
    • ไม่ expose object ภายใน
    abstract class ShiftRequestRepository {
      ShiftRequest? findById(ShiftRequestId id);
      void save(ShiftRequest request);
    }
    

    Repository ไม่ควรมี method อย่าง:

    • updateApprovalStatus()
    • setApprovedBy()

    นั่นคือการรั่วของ domain


    สรุปตอนที่ 5

    • Aggregate = ขอบเขตของความถูกต้อง
    • Aggregate Root = ผู้เฝ้าประตู
    • Invariant ต้องอยู่ใน aggregate

    ถ้าคุณแก้ entity ไหนก็ได้จากที่ไหนก็ได้

    นั่นแปลว่า domain ของคุณยังไม่มีเจ้าของ


    ตอนต่อไป

    ตอนที่ 6: Domain Service – เมื่อ logic ไม่ควรอยู่ใน Entity

    เราจะดูกรณีที่ logic บางอย่างไม่เหมาะจะอยู่ใน entity แต่ก็ไม่ควรไปอยู่ใน service ทั่วไป

  • Entity vs Value Object – อะไรควรมีตัวตนในระบบพนักงาน

    Entity vs Value Object – อะไรควรมีตัวตนในระบบพนักงาน

    ตอนที่ 4 ของซีรีส์ Domain-Driven Design ฉบับระบบธุรกิจจริง

    หลังจากตอนที่ 3 เราได้ภาษาเดียวกันระหว่างโค้ดกับธุรกิจแล้ว

    ตอนนี้ถึงเวลาลงมือ สร้าง Domain Model จริง ๆ

    คำถามแรกที่สำคัญมากคือ:

    สิ่งนี้ในระบบ ควรเป็น Entity หรือ Value Object ?

    ถ้าตัดสินใจผิดตั้งแต่ตรงนี้ Domain จะเริ่มบิดทันที


    ความเข้าใจผิดที่พบบ่อย

    นักพัฒนาระบบธุรกิจจำนวนมากมักคิดว่า:

    “อะไรก็ตามที่มาจาก database = Entity”

    ผลลัพธ์คือ:

    • ทุกอย่างมี id
    • ทุกอย่างแก้ค่าได้หมด
    • Domain กลายเป็นถุงข้อมูล (Anemic Model)

    DDD แยกสิ่งเหล่านี้ออกชัดเจนกว่า


    Entity คืออะไร

    Entity คือสิ่งที่:

    • มีตัวตนเฉพาะ (identity)
    • เปลี่ยนค่าได้ แต่ยังเป็น “สิ่งเดิม”
    • ธุรกิจสนใจว่า เป็นใคร มากกว่า มีค่าอะไร

    ในระบบพนักงาน ตัวอย่าง Entity ได้แก่:

    • Staff
    • ShiftRequest
    • Department

    แม้ข้อมูลจะเปลี่ยน แต่ identity ยังเดิม


    ตัวอย่าง Entity: Staff

    class Staff {
      final StaffId id;
      String name;
      StaffStatus status;
    
      void resign() {
        if (status == StaffStatus.resigned) {
          throw Exception('Staff already resigned');
        }
        status = StaffStatus.resigned;
      }
    }

    ประเด็นสำคัญ:

    • id คือหัวใจของ entity
    • behavior อยู่กับ entity
    • entity ปกป้องกฎของตัวเอง

    Value Object คืออะไร

    Value Object คือสิ่งที่:

    • ไม่มี identity
    • วัดค่าด้วย “ความเท่ากันของข้อมูล”
    • มักเป็น immutable

    ในระบบพนักงาน ตัวอย่าง Value Object ได้แก่:

    • EmailAddress
    • PhoneNumber
    • WorkingPeriod
    • ShiftTime

    ถ้าค่าเหมือนกัน = ถือว่าเหมือนกัน


    ตัวอย่าง Value Object: WorkingPeriod

    class WorkingPeriod {
      final DateTime start;
      final DateTime end;
    
      WorkingPeriod(this.start, this.end) {
        if (!end.isAfter(start)) {
          throw Exception('End must be after start');
        }
      }
    
      bool overlaps(WorkingPeriod other) {
        return start.isBefore(other.end) && end.isAfter(other.start);
      }
    }

    จุดสังเกต:

    • ไม่มี id
    • สร้างแล้วแก้ไม่ได้
    • กฎอยู่ในตัวมันเอง

    ทำไม Value Object ถึงสำคัญกับระบบธุรกิจ

    ถ้าคุณไม่ใช้ Value Object:

    • validation จะกระจัดกระจาย
    • logic ซ้ำ ๆ เต็มระบบ
    • test ยาก

    Value Object ทำให้:

    • กฎถูกเขียนครั้งเดียว
    • intent ชัดเจน
    • domain อ่านง่ายขึ้นมาก

    Entity + Value Object ทำงานร่วมกัน

    Entity ไม่ควรเก็บ primitive ดิบ ๆ เต็มไปหมด แต่ให้ใช้ชนิดข้อมูลที่เป็น value object มาทำงานร่วมกัน

    แทนที่จะเขียน:

    String email;
    DateTime shiftStart;
    DateTime shiftEnd;

    ให้เขียน:

    EmailAddress email;
    WorkingPeriod shiftPeriod;

    ตอนนี้ domain:

    • ปลอดภัยขึ้น เพราะ ค่า email หรือ shiftPeriod จะถูกตรวจสอบความถูกต้องของข้อมูลจาก value object ซึ่งก็คือ class EmailAddress และ WorkingPeriod เป็นต้น
    • อ่านเหมือนภาษาธุรกิจ
    • ลด bug เชิงกฎ

    Checklist ตัดสินใจ: Entity หรือ Value Object

    ถามตัวเองทีละข้อ:

    1. ธุรกิจสนใจ identity ของมันไหม?
    2. ถ้าข้อมูลเหมือนกัน ถือว่าเป็นอันเดียวกันได้ไหม?
    3. มันควรถูกแก้ไข หรือควรถูกแทนที่?

    ถ้า:

    • สนใจ identity → Entity
    • สนใจค่า → Value Object

    สรุปตอนที่ 4

    • Entity = ตัวตน + behavior
    • Value Object = ค่า + กฎ + immutability
    • ใช้ให้ถูกตั้งแต่ต้น = domain แข็งแรง

    ถ้าทุกอย่างในระบบเป็น Entity

    แปลว่าคุณยังไม่ได้ออกแบบ Domain จริง ๆ


    ตอนต่อไป

    ตอนที่ 5: Aggregate & Aggregate Root – กำแพงป้องกัน Domain ไม่ให้เละ

    เราจะเริ่มควบคุมขอบเขตของการแก้ไข และพูดถึง invariants ของระบบพนักงาน

  • จาก Imperative → Declarative → Domain Thinking

    จาก Imperative → Declarative → Domain Thinking

    ตอนที่ 2 ของซีรีส์ Domain-Driven Design ฉบับระบบธุรกิจจริง

    Theme หลัก: ระบบบริหารพนักงาน (Staff / Employee Management System)

    ในตอนที่แล้ว เราคุยกันว่า DDD คือการเปลี่ยนมุมมองการออกแบบระบบ โดยให้ Domain เป็นศูนย์กลาง

    ตอนนี้เราจะลงลึกขึ้นอีกขั้น ด้วยการดูว่า

    วิธีคิดในการเขียนโค้ดของเราเอง ส่งผลยังไงต่อการหายไปของ domain

    และทำไมการเปลี่ยนจาก Imperative → Declarative → Domain Thinking ถึงสำคัญมาก


    จุดเริ่มต้นของปัญหา: Imperative Thinking

    Imperative thinking คือการเขียนโค้ดแบบ:

    “ทำทีละขั้น ให้ได้ผลลัพธ์ตามที่ต้องการ”

    ตัวอย่างที่พบได้บ่อยในระบบพนักงาน:

    void approveShiftRequest(String requestId, String approverId) {
      final request = repository.findById(requestId);
    
      if (request == null) return;
      if (request.status != 'PENDING') return;
      if (!userService.isManager(approverId)) return;
    
      request.status = 'APPROVED';
      request.approvedBy = approverId;
      request.approvedAt = DateTime.now();
    
      repository.save(request);
    }
    

    โค้ดนี้ ทำงานได้ และดูเหมือนจะโอเค

    แต่ลองถามตัวเองว่า:

    • กฎของธุรกิจอยู่ตรงไหน?
    • อะไรคือสิ่งที่ “ห้าม” เกิดขึ้น?
    • ถ้ากฎเปลี่ยน ใครเป็นคนรับผิดชอบ?

    คำตอบคือ: ไม่มีที่ชัดเจน


    เมื่อโค้ดบอก “ยังไง” แต่ไม่บอก “อะไร”

    โค้ดแบบ Imperative มักมีปัญหาเหล่านี้:

    • อ่านแล้วรู้ลำดับ แต่ไม่รู้ความหมาย
    • Business rule ซ่อนอยู่ใน if-else
    • ชื่อ method ไม่สะท้อน intent ของธุรกิจ

    ในตัวอย่างก่อนหน้า:

    • status != 'PENDING' คือกฎ หรือแค่ technical check?
    • ใครควรมีสิทธิ์ approve จริง ๆ?
    • อนุมัติซ้ำได้ไหม?

    คำถามเหล่านี้ ไม่ถูกตอบใน domain แต่กระจายอยู่ใน service


    Declarative Thinking: บอก “อะไร” แทน “ยังไง”

    Declarative thinking คือการเขียนโค้ดแบบ:

    “ระบบควรอนุญาตให้เกิดอะไรได้บ้าง”

    ลองปรับมุมมองใหม่:

    shiftRequest.approve(by: approver);
    

    ประโยคนี้สั้น แต่มีความหมายทางธุรกิจชัดเจนมาก

    คำถามสำคัญคือ:

    แล้วกฎทั้งหมดหายไปไหน?

    คำตอบคือ: มันควรไปอยู่ใน Domain


    Domain Thinking: ให้ Domain ปกป้องกฎของตัวเอง

    ลองดูตัวอย่าง Domain Model ของ ShiftRequest

    class ShiftRequest {
      ShiftRequestStatus status;
      String? approvedBy;
      DateTime? approvedAt;
    
      void approve(Staff approver) {
        if (status != ShiftRequestStatus.pending) {
          throw Exception('Request is not pending');
        }
    
        if (!approver.isManager) {
          throw Exception('Only manager can approve');
        }
    
        status = ShiftRequestStatus.approved;
        approvedBy = approver.id;
        approvedAt = DateTime.now();
      }
    }
    

    ตอนนี้:

    • กฎอยู่กับข้อมูลที่มันควบคุม
    • ไม่มีใครข้ามกฎนี้ได้
    • อ่านโค้ดแล้วเข้าใจธุรกิจทันที

    นี่คือ Domain Thinking


    เปลี่ยนบทบาทของ Service และ Use Case

    เมื่อ domain แข็งแรง:

    • Service / Use Case ไม่ต้องรู้รายละเอียดกฎ
    • ทำหน้าที่แค่ ประสานงาน (orchestration)

    ตัวอย่าง Use Case:

    void approveShiftRequest(String requestId, String approverId) {
      final request = repository.findById(requestId);
      final approver = staffRepository.findById(approverId);
    
      request.approve(approver);
    
      repository.save(request);
    }
    

    Use Case อ่านเหมือนขั้นตอนธุรกิจ ไม่ใช่ logic ยิบย่อย


    ทำไม mindset นี้ถึงสำคัญกับระบบธุรกิจ

    ระบบธุรกิจมีลักษณะร่วมกัน:

    • กฎเปลี่ยนบ่อย
    • Exception เยอะ
    • ข้อยกเว้นสำคัญกว่ากรณีปกติ

    ถ้า logic กระจัดกระจาย:

    • แก้กฎหนึ่งครั้ง = เสี่ยงทั้งระบบ
    • Test ยาก
    • ไม่มีใครมั่นใจว่า logic ครบจริง

    Domain Thinking ทำให้:

    • กฎมีเจ้าของชัดเจน
    • Test domain ได้โดยไม่ต้องพึ่ง UI หรือ DB
    • ระบบโตโดยไม่เสียรูป

    สรุปตอนที่ 2

    • Imperative: บอกระบบว่า ทำยังไง
    • Declarative: บอกระบบว่า อะไรควรเกิด
    • Domain Thinking: ให้ domain เป็นผู้ตัดสิน

    ถ้า Domain ของคุณไม่มี behavior

    นั่นแปลว่ากฎของธุรกิจอาจกำลังลอยอยู่ผิดที่


    ตอนต่อไป

    ตอนที่ 3: Ubiquitous Language – ภาษาที่โค้ดกับธุรกิจพูดตรงกัน

    เราจะเริ่มจากการตั้งชื่อผิด ๆ ในระบบพนักงาน แล้วค่อยแก้ให้โค้ดกลายเป็นภาษาของธุรกิจจริง ๆ

  • Ubiquitous Language – ภาษาที่โค้ดกับธุรกิจพูดตรงกัน

    Ubiquitous Language – ภาษาที่โค้ดกับธุรกิจพูดตรงกัน

    ตอนที่ 3 ของซีรีส์ Domain-Driven Design ฉบับระบบธุรกิจจริง

    Theme หลัก: ระบบบริหารพนักงาน (Staff / Employee Management System)

    ในตอนที่ 2 เราเห็นแล้วว่า Domain Thinking คือการให้ domain เป็นผู้ตัดสินกฎของธุรกิจ

    แต่ถึง domain จะอยู่ถูกที่แล้ว ระบบก็ยังพังได้ ถ้าเราใช้ “ภาษา” ผิด

    DDD เรียกภาษานี้ว่า Ubiquitous Language


    ปัญหาที่แท้จริง: เราคุยกันคนละภาษา

    ลองนึกถึงบทสนทนาแบบนี้ในระบบพนักงาน:

    • ฝ่าย HR: “พนักงานยื่น คำขอเปลี่ยนกะ ได้วันละ 1 ครั้ง”
    • Developer: “โอเค เดี๋ยวเช็ค field request_count”
    • Code: canEdit = status != 2 && role != 3

    ทุกคนคิดว่าตัวเองเข้าใจตรงกัน แต่จริง ๆ แล้ว:

    • ธุรกิจพูดถึง คำขอ (Request)
    • โค้ดพูดถึง ตัวเลขและสถานะลอย ๆ

    ผลลัพธ์คือ ระบบที่:

    ไม่มีใครมั่นใจว่าโค้ดสะท้อนกฎจริงครบหรือยัง


    Ubiquitous Language คืออะไร

    Ubiquitous Language (Ubiquitous อ่านว่า ยูบิค ควิเทิส) คือ:

    ภาษาชุดเดียว ที่ถูกใช้ ร่วมกัน ระหว่าง

    • คนธุรกิจ
    • เอกสาร
    • โค้ด
    • การทดสอบ

    ถ้าธุรกิจเรียกว่า “คำขอเปลี่ยนกะ”

    โค้ดก็ต้องเรียกว่า:

    • ShiftChangeRequest
    • ไม่ใช่ RequestModel
    • ไม่ใช่ Transaction

    ชื่อในโค้ด ไม่ควรแปล หรือย่อความหมายของธุรกิจ


    ตัวอย่างชื่อที่ทำให้ Domain พัง

    ในระบบพนักงาน มักเจอชื่อแบบนี้:

    • status = 1, 2, 3
    • type = ‘A’
    • flag = true
    • role = 99

    โค้ดอาจทำงานได้ แต่คำถามคือ:

    • status = 2 แปลว่าอะไร?
    • ใครเป็นคนตัดสิน?
    • กฎนี้มาจากไหน?

    ถ้าต้องเปิดเอกสารหรือถามคนอื่นทุกครั้ง แปลว่า ภาษาไม่ ubiquitous


    เปลี่ยนตัวเลขให้เป็นภาษาธุรกิจ

    แทนที่จะเขียนแบบนี้:

    if (request.status != 1) return;

    ให้เขียนแบบนี้:

    enum ShiftRequestStatus {
      pending,
      approved,
      rejected,
      cancelled,
    }
    
    if (request.status != ShiftRequestStatus.pending) return;

    ตอนนี้โค้ด:

    • อ่านแล้วรู้ความหมายทันที
    • ตรงกับภาษาที่ HR ใช้
    • ลดการตีความผิด

    Ubiquitous Language ต้องอยู่ทุกที่

    ภาษานี้ไม่ควรอยู่แค่ใน code

    แต่ควรอยู่ใน:

    • ชื่อ class / method
    • test case
    • commit message
    • document

    ตัวอย่าง test:

    test('manager can approve pending shift request', () {
      // ...
    });

    ถ้าอ่าน test แล้ว HR เข้าใจได้ แปลว่าคุณมาถูกทางแล้ว


    ระวังคำที่ดู technical เกินไป

    คำบางคำดูเหมือนจะโอเค แต่จริง ๆ แล้วอันตราย:

    • process
    • handle
    • manage
    • data

    เช่น:

    • processRequest() → ทำอะไร?
    • handleStaff() → handle ยังไง?

    ให้ถามตัวเองว่า:

    ธุรกิจใช้คำนี้จริงไหม?

    ถ้าไม่ ให้เปลี่ยน


    สร้างภาษาไปพร้อมกับธุรกิจ

    Ubiquitous Language ไม่ใช่สิ่งที่คิดครั้งเดียวแล้วจบ

    มันต้อง:

    • evolve ตามความเข้าใจ
    • ถูกแก้เมื่อพบว่าคำเดิมไม่ชัด
    • กล้าลบชื่อเก่า แม้จะเขียนไปแล้ว

    การ refactor ชื่อ:

    ไม่ใช่งาน cosmetic แต่เป็นงานออกแบบ domain


    สรุปตอนที่ 3

    • ภาษา คือโครงสร้างของความคิด
    • ชื่อที่ไม่ชัด = domain ที่พัง
    • โค้ดควรอ่านเหมือนประโยคธุรกิจ

    ถ้าคุณอธิบายระบบโดยเปิดโค้ดให้ฝ่ายธุรกิจดูไม่ได้

    นั่นแปลว่าภาษาของระบบยังไม่ ubiquitous


    ตอนต่อไป

    ตอนที่ 4: Entity vs Value Object – สิ่งไหนควรมีตัวตนในระบบพนักงาน

    เราจะเริ่มแตก domain model ของระบบพนักงานอย่างจริงจัง ว่าอะไรคือ entity อะไรคือ value object

  • วิวัฒนาการของ Backend, Frontend และ Full-stack Developer

    วิวัฒนาการของ Backend, Frontend และ Full-stack Developer

    หลายคนที่ทำงานสายพัฒนาซอฟต์แวร์อาจคุ้นเคยกับคำว่า Backend Developer, Frontend Developer และ Full-stack Developer เป็นอย่างดี แต่คำถามที่น่าสนใจคือ

    แนวคิดเหล่านี้เริ่มต้นมาตั้งแต่เมื่อไร และเกิดขึ้นได้อย่างไร?

    บทความนี้จะพาย้อนดูพัฒนาการของบทบาทนักพัฒนาซอฟต์แวร์ ตั้งแต่อดีตจนถึงปัจจุบัน เพื่อให้เข้าใจว่า “การแบ่ง role” ไม่ใช่เรื่องบังเอิญ แต่เป็นผลลัพธ์ของวิวัฒนาการทางเทคโนโลยี


    1. ยุคแรก: ยังไม่มี Backend / Frontend (1960s–1990s)

    ในยุคของ Mainframe และ Desktop Application

    • โปรแกรมส่วนใหญ่เป็นแบบ monolithic
    • นักพัฒนา 1 คนดูแลทุกอย่าง
      • ส่วนติดต่อผู้ใช้ (UI)
      • Business Logic
      • Database

    ตัวอย่างเทคโนโลยีในยุคนี้:

    • COBOL, C, Pascal
    • Visual Basic
    • Delphi

    ในช่วงเวลานี้ยังไม่มีการเรียกตำแหน่งว่า backend หรือ frontend ทุกคนคือ “โปรแกรมเมอร์” ที่ต้องทำงานครบทั้งระบบ


    2. Web 1.0: Backend Developer เริ่มมีตัวตน (ประมาณ 1995–2005)

    เมื่ออินเทอร์เน็ตและเว็บไซต์เริ่มแพร่หลาย ระบบส่วนใหญ่ทำงานในรูปแบบ:

    Browser ส่ง request → Server ประมวลผล → ส่ง HTML กลับ

    เทคโนโลยีเด่นในยุคนี้:

    • PHP
    • ASP / ASP.NET
    • JSP / Servlet
    • CGI, Perl

    ฝั่ง Server ต้องดูแล:

    • การประมวลผลข้อมูล
    • การเชื่อมต่อฐานข้อมูล
    • การสร้าง HTML แบบ dynamic

    แนวคิดของ Backend Developer จึงเริ่มชัดเจนในยุคนี้ แม้ในทางปฏิบัติ คนคนเดียวมักจะยังทำทั้ง HTML, CSS และ SQL อยู่ก็ตาม


    3. Web 2.0: การกำเนิดของ Frontend Developer (ประมาณ 2005–2012)

    จุดเปลี่ยนสำคัญของเว็บยุคใหม่คือ:

    • AJAX
    • JavaScript มีบทบาทมากขึ้น
    • ประสบการณ์ผู้ใช้ (UX) กลายเป็นเรื่องสำคัญ

    เครื่องมือยอดนิยม:

    • jQuery
    • Prototype
    • CSS Framework

    เริ่มเกิดการแยกบทบาทชัดเจน:

    • Backend Developer: ดูแล logic, database, server
    • Frontend Developer: ดูแล HTML, CSS, JavaScript และ interaction บน browser

    ตั้งแต่ช่วงนี้เป็นต้นมา คำว่า Frontend Developer เริ่มถูกใช้เป็นทางการในองค์กร


    4. ยุค SPA และ API: Frontend / Backend แยกขาด (ประมาณ 2012–2018)

    การมาของ Single Page Application (SPA) และ REST API ทำให้โครงสร้างระบบเปลี่ยนไปอย่างสิ้นเชิง

    Frontend:

    • Angular
    • React
    • Vue

    Backend:

    • API-first design
    • Node.js
    • Django, Rails
    • Spring Boot

    ลักษณะเด่นของยุคนี้คือ:

    • Frontend ไม่ยุ่งกับ database
    • Backend ไม่สนใจหน้าตา UI
    • สื่อสารกันด้วย JSON ผ่าน API

    บทบาท Frontend และ Backend ถูกแยกอย่างชัดเจนทั้งในเชิงเทคนิคและโครงสร้างทีม


    5. การกลับมาของ Full-stack Developer (ประมาณ 2014–ปัจจุบัน)

    แม้บทบาทจะแยกชัดขึ้น แต่ในเวลาเดียวกัน คำว่า Full-stack Developer ก็กลับมาได้รับความนิยมอีกครั้ง โดยเฉพาะใน:

    • Startup
    • ทีมขนาดเล็ก
    • Product-oriented team

    Full-stack Developer ในยุคใหม่นี้หมายถึง:

    • เข้าใจ frontend framework
    • เขียน backend และ API ได้
    • ออกแบบ database ได้
    • deploy และดูแลระบบเบื้องต้นได้

    ไม่จำเป็นต้องเก่งที่สุดทุกด้าน แต่สามารถทำงานได้ครบทั้ง flow ของระบบ


    6. ยุคปัจจุบัน: บทบาทยิ่งละเอียดและยืดหยุ่น

    ปัจจุบันเริ่มมีการแตกบทบาทย่อย เช่น:

    • Frontend (Web / Mobile)
    • Backend (API / Platform / Infrastructure)
    • Full-stack แบบเอนเอียง (Frontend-leaning / Backend-leaning)

    องค์กรขนาดใหญ่ยังคงเน้น specialist ขณะที่ทีมเล็กและ startup ให้คุณค่ากับ full-stack ที่เข้าใจระบบทั้งภาพรวม


    บทสรุป

    แนวคิดของ Backend, Frontend และ Full-stack Developer ไม่ได้เกิดจากการตั้งชื่อเฉย ๆ แต่เป็นผลจากการเปลี่ยนแปลงของเทคโนโลยี วิธีสร้างซอฟต์แวร์ และขนาดของทีม

    • อดีต: คนหนึ่งทำทุกอย่าง
    • กลางทาง: แยกตามความซับซ้อน
    • ปัจจุบัน: ผสมผสานตามบริบทของทีมและธุรกิจ

    สุดท้ายแล้ว ไม่ว่าชื่อตำแหน่งจะเป็นอะไร สิ่งสำคัญที่สุดคือ ความเข้าใจระบบโดยรวม และการเลือกบทบาทให้เหมาะกับเป้าหมายของทีมและผลิตภัณฑ์

  • Domain-Driven Design คืออะไร (แบบไม่ใช่ตำรา)

    Domain-Driven Design คืออะไร (แบบไม่ใช่ตำรา)

    ตอนที่ 1 ของซีรีส์ Domain-Driven Design ฉบับคนทำแอป & เกมจริง

    ถ้าคุณเป็น developer ที่เขียนระบบมานานพอ คุณน่าจะเคยเจอสถานการณ์ประมาณนี้:

    • โค้ดเขียนถูกหมด แต่ระบบแก้ยาก
    • เพิ่ม feature นิดเดียว กระทบไปทั้งระบบ
    • business rule กระจัดกระจายอยู่ใน controller, service, widget, หรือแม้แต่ SQL
    • ไม่มีใครกล้าแตะโค้ดเก่า เพราะกลัวพัง

    คำถามคือ ปัญหานี้เกิดจากภาษา หรือ framework จริงไหม?

    คำตอบของ Domain-Driven Design (DDD) คือ: ไม่ใช่

    ปัญหาหลักคือ เราออกแบบระบบจากมุมมองที่ผิดตั้งแต่ต้น


    Domain-Driven Design ไม่ใช่ Architecture

    หลายคนพอได้ยินคำว่า DDD จะนึกถึงสิ่งเหล่านี้ทันที:

    • Clean Architecture
    • Hexagonal Architecture
    • Folder ซับซ้อนหลายชั้น
    • Interface เต็มไปหมด

    แต่จริง ๆ แล้ว DDD ไม่ใช่ architecture และไม่ใช่ pattern ชุดหนึ่งที่ต้องทำตาม

    DDD คือ แนวคิดในการมองและออกแบบระบบ โดยให้ Domain (กฎของธุรกิจ) เป็นศูนย์กลาง

    Architecture เป็นเพียง เครื่องมือ ที่ช่วยให้แนวคิดนี้อยู่รอดในโค้ด


    แล้ว “Domain” คืออะไรกันแน่?

    Domain ไม่ใช่ database

    Domain ไม่ใช่หน้าจอ UI

    Domain คือ:

    ความรู้ กฎ เงื่อนไข และความหมายของสิ่งที่ระบบนี้ถูกสร้างขึ้นมาเพื่อจัดการ

    ตัวอย่าง:

    • ระบบพนักงาน → กฎการขอ shift, การอนุมัติ, เงื่อนไขเวลา
    • ระบบจอง → เงื่อนไขการยกเลิก, การคืนเงิน, การทับซ้อนของเวลา

    สิ่งเหล่านี้ ไม่ควรขึ้นกับ UI, database หรือ framework ใด ๆ


    ปัญหาของระบบแบบ Data‑Driven / UI‑Driven

    ระบบส่วนใหญ่เริ่มจากคำถามแบบนี้:

    • มีหน้าจออะไรบ้าง?
    • ต้องมีตารางอะไร?
    • API endpoint หน้าตาเป็นยังไง?

    แล้วโค้ดก็มักจะออกมาในรูปแบบ:

    Controller
      → Service
        → Repository
          → Database
    

    ดูเหมือนจะเป็นระเบียบ แต่ปัญหาคือ:

    • Business rule ถูกยัดไว้ใน service แบบปนกันหมด
    • Entity เป็นแค่ถุงข้อมูล (Anemic Model)
    • ชื่อ class/field สื่อความหมายผิดจากธุรกิจจริง

    ผลลัพธ์คือ:

    ระบบ “ทำงานได้” แต่ไม่สะท้อนความจริงของ domain


    DDD เปลี่ยนคำถามตั้งต้น

    แทนที่จะถามว่า:

    “จะเก็บข้อมูลยังไงดี?”

    DDD จะเริ่มจากคำถามว่า:

    “ในโลกจริง สิ่งนี้ ควรทำอะไรได้ และ อะไรที่ห้ามเกิดขึ้น

    เช่น:

    • พนักงานขึ้นกะได้กี่ครั้งต่อวัน?
    • คำขอถูกแก้ไขได้เมื่อไหร่?
    • ใครมีสิทธิ์อนุมัติ?

    คำถามเหล่านี้คือ Domain Question ไม่ใช่ Technical Question


    หัวใจของ DDD ในประโยคเดียว

    Business Rule ต้องอยู่ใน Domain และ Domain ต้องไม่พึ่งพาโลกภายนอก

    เมื่อทำได้:

    • โค้ดจะอ่านเหมือนเอกสารอธิบายธุรกิจ
    • การเปลี่ยนกฎไม่กระทบทั้งระบบ
    • ระบบโตได้โดยไม่เละ

    DDD ไม่ได้ทำให้เขียนโค้ดน้อยลง

    ความจริงที่ต้องยอมรับคือ:

    • โค้ดจะเยอะขึ้น
    • ต้องคิดมากขึ้น
    • ช่วงแรกจะช้าลง

    แต่สิ่งที่ได้คือ:

    • ความชัดเจน
    • ความมั่นใจในการแก้ระบบ
    • ระบบที่อธิบายตัวเองได้

    DDD คือการ ลงทุนระยะยาว ไม่ใช่ shortcut


    ตอนต่อไป

    ตอนที่ 2: จาก Imperative → Declarative → Domain Thinking

    เราจะเริ่มดูว่า mindset แบบเดิมของการเขียนโค้ด ทำให้ domain หายไปจากระบบได้ยังไง และจะเปลี่ยนมุมมองยังไงให้โค้ดสะท้อนความจริงมากขึ้น


    ถ้าเราเคยรู้สึกว่า “โค้ดเราไม่ได้ผิด แต่ระบบมันดูไม่ใช่”

    DDD น่าจะเป็นสิ่งที่คุณกำลังมองหาอยู่

  • Callable Object ใน Dart คืออะไร ใช้ทำไม

    Callable Object ใน Dart คืออะไร ใช้ทำไม

    Callable Object ใน Dart

    Callable Object คือ object ที่ถูกเรียกเหมือน function ได้
    โดยการประกาศ method พิเศษชื่อว่า call()

    class Adder {
    
      final int base;
    
      Adder(this.base);
    
      int call(int value) {
    
        return base + value;
    
      }
    
    }

    เมื่อสร้าง instance แล้ว สามารถเรียกแบบนี้ได้ทันที

    final add10 = Adder(10);
    
    add10(5); // 15

    แม้ add10 จะเป็น object
    แต่ Dart อนุญาตให้เรียกมันเหมือน function


    ทำไมต้องใช้ Callable Object

    เหตุผลหลักคือ เราอยากได้ความง่ายแบบ function
    แต่ยังคงโครงสร้างและพลังของ class

    เหตุผลที่ควรใช้

    • เก็บ state ได้ (function ทำไม่ได้)
    • มี dependency ได้ (เช่น repository, service)
    • ชื่อ class ช่วยสื่อความหมายของ logic
    • เหมาะกับ business logic / use case

    ตัวอย่างเช่น

    class LoginUser {
    
      final AuthRepository repo;
    
      LoginUser(this.repo);
    
      Future<User> call(String email, String password) {
    
        return repo.login(email, password);
    
      }
    
    }

    เวลาใช้งานจะอ่านง่ายมาก

    await loginUser(email, password);

    สรุป

    Callable Object =
    เขียนเหมือนเรียก function
    แต่ได้ความสามารถของ class

    เหมาะกับ:

    • use case
    • business logic
    • โค้ดระดับ application

    ไม่เหมาะกับ:

    • utility function สั้น ๆ
    • logic ง่าย ๆ ไม่มี state