Blog

  • 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
  • SwiftUI App Scene และ View ใช้สร้าง app ได้ทั้งแอปแล้ว

    SwiftUI App Scene และ View ใช้สร้าง app ได้ทั้งแอปแล้ว

    ในงาน wwdc 2020 ได้ประกาศว่า SwiftUI สามารถนำมาสร้าง App ทั้ง app โดยไม่ต้องพึ่งพา framework อื่นๆ ได้แล้ว ซึ่งแต่เดิมตอนเปิดตัวในปี 2019 นั้น swiftUI ยังต้องพึ่งพา framework อื่นๆ เช่น UIKit, AppKit ในการสร้าง control บางอย่างเช่น tabview เป็นต้น

    แต่ในปีนี้ 2020 เราสามารถสร้าง app ทั้ง app โดยใช้ swiftUI framework อย่างเดียวได้ โดยทางทีมพัฒนาได้อธิบายว่า จะมีส่วนประกอบ คือ App Scene และ View ที่นำมาประกอบกันเป็น app ได้

    SwiftUI App Scene และ View

    View : ชิ้นส่วนของ User interface เช่น button, text รวมถึง container ด้วย เป็นชิ้นส่วนชิ้นเล็กๆ ที่จะนำมาประกอบกัน จนกลายเป็นส่วนติดต่อผู้ใช้งาน

    Scene : เป็น container ให้กับ view ซึ่ง scene ต่างๆนี้ ระบบจะเป็นตัวพิจารณาเองว่า ในแต่ละ platform จะแสดงหน้าตาออกมาได้อย่างไร ทางทีมพัฒนาได้สร้าง built-in scene สำเร็จรูปมาให้เราใช้บางส่วน เช่น WindowGroup, DocumentGroup, Setting เป็นต้น

    App : เป็นส่วนที่คลุม Scene และ view ทั้งหมด ถือเป็นจุดตั้งต้นของ App

    ความสัมพันธ์ของ App scene และ View

    App scene และ view เป็น subset กัน โดยที่ App สามารถมีหลายๆ scene และ scene ก็สามารถมีหลายๆ scene หรือมีหลายๆ view อยู่ข้างในได้ ซึ่งทาง apple ก็ทำแผนผังมาให้ดูได้ดีดังภาพด้านล่าง

    ภาพจาก video ในงาน wwdc 2020

    ตัวอย่าง Code

    ตัวอย่าง code ด้านล่าง แสดง App ,scene view ใน swiftUI block

    @main struct MyBookApp: App {
        var body: some Scene {
            WindowGroup {
                TabView {
                    ComputerBookView()
                    ArtBookView()
                    ComicBookView()
                }
            }
        }
    }

    MyBookApp เป็นส่วนของ App

    WindowGroup เป็น scene

    TabView เป็น View ที่มี ComputerBookView, ArtBookView,ComicBookView ซ้อนอยู่

    Reference and other link

    https://developer.apple.com/documentation/swiftui/app-structure-and-behavior

    Some Keyword ใน SwiftUI คืออะไร และใช้เพื่ออะไร

    Udemy – Online Programming course

    Home

    ภาพประกอบ ภาพโดย William Iven จาก Pixabay

  • Some Keyword ใน SwiftUI คืออะไร และใช้เพื่ออะไร

    Some Keyword ใน SwiftUI คืออะไร และใช้เพื่ออะไร

    Some Keyword ใน SwiftUI นั้น เป็น keyword ที่เริ่มมีในภาษา swift version 5.1 เป็นต้นมา ซึ่งหากดูในเอกสารของ swift.org แล้ว จะอยู่ในส่วนของหัวข้อ Opaque Type

    การระบุ keyword some ที่ด้านหน้าของ return type ไหนก็ตาม return type นั้นจะกลายเป็น Opaque Type

    “Some” Keyword

    เมื่อสร้าง โปรเจ็คใหม่โดยใช้ swiftUI หรือการ แตก subview (extract subView) เราจะเห็น Keyword some ถูกนำมาใช้ที่ตัวแปร body

    Keyword some ถูกนำมาใช้ในส่วนของการประกาศตัวแปร body ดังนี้

    ตัวอย่างเช่น var body: some View

    import SwiftUI
    
    struct ContentView: View {
        var body: some View {
            Text("Hello, world!")
                .padding()
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    Some Keyword ใน SwiftUI คืออะไร

    some จะถูกระบุหน้า return type โดยถ้าระบุ some ไว้ ก็หมายความว่า ชนิดข้อมูลที่ return กลับนั้น จะเป็นชนิดข้อมูลอะไรก็ได้ ที่สอดคล้อง(conform to) กับ protocol หลัง keyword some

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

    var body: some View {
            Text("Hello, world!")
        }

    หมายความว่า ชนิดข้อมูล(Type) ที่ส่งกลับมายังตัวแปล Body นั้น จะเป็นชนิดข้อมูลอะไรก็ได้ โดยที่ต้องสอดคล้องกับ protocol View

    ดังนั้นเราจึงสามารถเอา Text(“Hello, world!”) ใส่เข้าไปได้ เพราะ Text นั้น conform กับ View นั่นเอง

    เราสามารถดูว่า Type conform กันหรือเปล่าจากเอกสาร developer document ในเว็บไซต์ apple

    เปิดเอกสารของ Apple developer แล้วเลื่อนลงไปข้างล่างจะพบหัวข้อ conforms To ที่ระบุว่า conform to view

    Type ไหนบ้างที่ conform กับ View

    อ้างอิง

    https://stackoverflow.com/questions/56433665/what-is-the-some-keyword-in-swiftui

    https://www.vadimbulavin.com/opaque-return-types-and-the-some-keyword-in-swift/

    Link อื่นๆ

    Udemy

    Home

    ขอบคุณภาพโดย Eerina Hart จาก Pixabay

  • Swift Grammar – วิธีอ่านเอกสารอ้างอิงของ swift ให้เข้าใจ

    Swift Grammar – วิธีอ่านเอกสารอ้างอิงของ swift ให้เข้าใจ

    ในเอกสารอ้างอิงของภาษา swift บนเว็บไซต์ swift.org นั้น จะมีการเขียนการใช้งานภาษา swift ในรูปแบบแบบมาตรฐานเรียกว่า Swift grammar (เหมือน grammar การใช้งานภาษาอังกฤษ) การรู้จักกับแกรมมาของภาษา swfit จะช่วยให้อ่านเอกสารอ้างอิงได้เข้าใจมากขึ้น

    Swift Grammar notation

    คำนิยาม

    Grammar Production : รูปแบบที่สามารถเป็นไปได้ อยู่ด้านขวาของลูกศร (→)

    Grammar Production Rule : กฎของ grammar ในเรื่องนั้นๆ ซึ่งก็คือข้อความ grammar ทั้งหมดนั่นเอง

    สัญลักษณ์ที่ใช้ในเอกสารอ้างอิงของ swift มีดังนี้

    1 ลูกศร (→)

    ลูกศรในเอกสาร swift (→) ใช้ระบุ grammar production สามารถอ่านได้ว่า “Can consist of” หรือ “สามารถที่จะมี”

    ยกตัวอย่าง

    getter-setter-block → { getter-clause setter-clause opt } | { setter-clause getter-clause }

    อ่านว่า getter-setter-block สามารถที่จะมี getter block + setter block (opt – setter block อาจจะมีหรือไม่มีก็ได้) หรือ setter block + getter block

    2 ตัวอักษรเอียง

    ตัวอักษรเอียง (italic text) หมายถึง Syntactic categories (ประเภทของการสร้างประโยค) สามารถระบุได้ทั้งฝั่งซ้ายและฝั่งขวาของลูกศร (คือเอกสารจะเขียนไว้ได้ทั้งซ้ายและขวาของ grammar production rule ถ้าอยู่ฝั่งขวามักจะเป็น link ดังตัวอย่างด้านล่าง)

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

    getter-setter-block → { getter-clause setter-clause opt } | { setter-clause getter-clause }

    syntactic category ในประโยคนี้คือ

    getter-setter-block

    getter-clause (syntactic category ที่เป็น link ไปดูที่ไปที่มาต่อได้)

    setter-clause

    3 ตัวอักษรหนา

    ตัวอักษรหนา (boldface constant width text) จะปรากฎเฉพาะด้านขวาของ production grammar rule

    บ่งบอกถึงเครื่องหมายวรรคตอน (Punctuation) หรือ คำ Keyword ต่างๆ ตัวอย่างเช่น วงเล็บปีกกา ( { } ), true/false, nil เป็นต้น

    ตัวอย่าง

    boolean-literal → true | false

    หมายความว่า ค่าของตัวแปร boolean จะสามารถมี true หรือ false

    4  vertical bars (|) = “หรือ”

    Vertical bar (เส้นขีดแนวดิ่ง) บ่งบอกถึงคำว่า “หรือ”

    มันเป็น alternate production rule คือ กฎหรือรูปแบบอาจจะเป็นไปได้สำหรับ grammar production นั้นๆ

    กรณีที่มี alternate production rule หลายๆ แบบ ก็จะเขียน grammar production rule แยกบรรทัดกัน

    ตัวอย่างการแตกประโยค “หรือ”

    GRAMMAR OF A GETTER-SETTER BLOCK

    getter-setter-block → { getter-clause setter-clause opt } | { setter-clause getter-clause }

    แตกออกเป็นสองบรรทัดได้ดังนี้

    getter-setter-block → { getter-clause setter-clause opt }

    getter-setter-block → { setter-clause getter-clause }

    ซึ่งทั้งสองแบบก็ให้ความหมายเดียวกัน

    5 Opt

    Opt (ตัวเอียง) ใช้ระบุใน grammar production (ด้านขวาของลูกศร) บอกถึงจะมีหรือไม่มีก็ได้ (option)

    บทความอื่นๆ

    รู้จักภาษา Swift กันก่อน

    Cocoapods ตอนที่ 1 – แหล่งรวม Library สำหรับนักพัฒนา

    Udemy

  • Decorator pattern คืออะไร

    Decorator pattern คืออะไร

    Decorator pattern คือรูปแบบการเขียนโปรแกรมให้สามารถเพิ่มความสามารถให้กับ object โดยไม่ต้องแก้ไข class โดยเราสามารถ instantiate new class เข้าไปยัง object ของ class เดิม เพื่อเพิ่มเติมความสามารถให้มากขึ้นโดยไม่ต้องแก้ไข code ของ class เดิมเลย

    ดูตัวอย่างการเรียกใช้ Decorator pattern กันก่อน

    ลองดู code ตัวอย่างการใช้งานกันก่อน โดยจะยกตัวอย่างคอมแบบประกอบ

    สมมติว่าเรากำลังเขียน class ของร้านคอมที่มี คอมพิวเตอร์จัดชุดอยู่แล้วให้ลูกค้าสามารถเพิ่มอุปกรณ์ (upgrad)เข้ามาได้ ถ้าหากจะใช้ pattern การเรียกใช้งาน class / object จะเป็นดังนี้

    DefaultComputerSet1 basicComputer = new DefaultComputerSet1();
                ComputerUpgrade upgradeSet = new ComputerUpgrade(basicComputer);
                upgradeSet = new GeforceRTX2080(upgradeSet);
                upgradeSet = new CreativeSoundAE7(upgradeSet);
    
    
                Console.WriteLine("Description : " + upgradeSet.GetDescription());
                Console.WriteLine("Price : " + upgradeSet.GetPrice());
            

    ผลลัพท์

    Description และราคา แสดงใน console

    Summary : DefaultComputerSet 1 + Geforce RTX 2080 + Creative Sound Blaster AE7

    Price : 83900

    จาก Code ด้านบนจะเห็นว่า object upgradeSet จะมีการประกาศ new Class เพิ่มเข้ามาได้เรื่อยๆ (new GeforceRTX2080, new CreativeSoundAE7…..)

    แล้วสุดท้ายเราก็เรียกใช้ getDescription, getPrice เพื่อดูว่าเลือกอุปกร์อะไรไป มันก็จะรวมข้อมูล description, price จาก ทุกๆ class ที่เรา add เข้าไปใน object ได้

    จุดสังเกตุของ pattern นี้จะเห็นว่าเราไม่ต้องไปแก้ class เดิมเลย (DefaultComputerSet1) แต่เราจะ เพิ่ม class เข้าไปเรื่อยๆ ถ้าหากมีอุปกรณ์ใหม่ๆ เป็นร้อยเป็นพัน มันก็จะไม่มีผลกระทบต่อ class เดิมด้วย นี่คือข้อดีของ pattern นี้

    Decorator pattern code example

    Code ด้านล่างประกอบด้วย Class และ interface ดังนี้

    IComputer : เป็น interface ที่ใครสืบทอดไปก็ตามจะต้อง implement method : getDescription และ getPrice

    DefaultComputerSet : เป็น class ที่กำหนด set ตั้งต้น และราคา เหมือนเวลาที่ร้านคอมมีคอมวางไว้หน้าร้าน แล้วถามว่าเราจะ upgrade หรือเปล่า

    ComputerUpgrade : เป็น abstract class ของอุปกรณ์ upgrade ต่างๆ

    GeforceRTX2080 : เป็น class ที่สืบต่อมาจาก ComputerUpgrade จะใช้ instantiate เพิ่มเข้าไปใน basicComputer object ในกรณีสมมติว่าจะซื้อการ์ดจอด้วย

    CreativeSoundAE7 : เป็น class ที่สืบต่อมาจาก ComputerUpgrade จะใช้ instantiate เพิ่มเข้าไปใน basicComputer object ในกรณีสมมติว่าจะซื้อ sound card ด้วย

    using System;
    
    using AppKit;
    using Foundation;
    
    namespace test2
    {
        public interface IComputer
        {
            string GetDescription();
            float GetPrice();
        }
        public class DefaultComputerSet1 : IComputer {
    
    
            public string GetDescription()
            {
                return "Default computer set 1";
            }
    
            public float GetPrice()
            {
                return 30000f;
            }
        }
    
        public abstract class ComputerUpgrade : IComputer
        {
            private readonly IComputer _computer;
            public ComputerUpgrade(IComputer computer)
            {
                _computer = computer;
            }
    
            public virtual string GetDescription()
            {
                return _computer.GetDescription();
            }
    
            public virtual float GetPrice()
            {
                return _computer.GetPrice();
            }
        }
    
        public class GeforceRTX2080 : ComputerUpgrade{
    
            public GeforceRTX2080(IComputer computer) : base(computer)
            {
                
            }
    
            public override string GetDescription()
            {
                return base.GetDescription() + " + Geforce RTX 2080";
            }
    
            public override float GetPrice()
            {
                return base.GetPrice() + 47900f;
            }
        }
    
        public class CreativeSoundAE7 : ComputerUpgrade
        {
            public CreativeSoundAE7(IComputer computer) : base(computer)
            {
            }
    
            public override string GetDescription()
            {
                return base.GetDescription() + " + Creative Sound Blaster AE7";
            }
    
            public override float GetPrice()
            {
                return base.GetPrice() + 6000f;
            }
        }
    
        public partial class ViewController : NSViewController
        {
            public ViewController(IntPtr handle) : base(handle)
            {
            }
    
            public override void ViewDidLoad()
            {
                base.ViewDidLoad();
    
    
                // Do any additional setup after loading the view.
            }
    
            partial void ClickedButton(NSObject sender)
            {
               
                DefaultComputerSet1 basicComputer = new DefaultComputerSet1();
                ComputerUpgrade upgradeSet = new GeforceRTX2080(basicComputer);
                upgradeSet = new CreativeSoundAE7(upgradeSet);
    
    
                Console.WriteLine("Description : " + upgradeSet.GetDescription());
                Console.WriteLine("Price : " + upgradeSet.GetPrice());
            }
    
    
            public override NSObject RepresentedObject
            {
                get
                {
                    return base.RepresentedObject;
                }
                set
                {
                    base.RepresentedObject = value;
                    // Update the view, if already loaded.
                }
            }
        }
    }
    
    

    ทิ้งท้าย

    Pattern นี้มีประโยชน์ในกรณีที่อยากเพิ่มความสามารถ/ behavior แต่ไม่ไปยุ่งกับ class เดิม โดยเราจะทำการ instantiate class ใหม่เข้าไปใน object เดิม ทำให้ code ที่เขียนขึ้นมีคุณสมบัติ open/close ตรงตาม SOLID Principle

    บทความและคอร์สอื่นๆ

    Udemy

    c# sealed class คืออะไร

    C# Abstract class คืออะไร