Application Architecture — สถาปัตยกรรมที่ปกป้อง domain model
จนถึงตอนนี้คอร์สเราพูดถึงแต่ “วิธีสร้าง model” ที่เข้าใจ problem domain — แต่ model ที่ดีจะไร้ค่าทันที หากถูกกลืนหายไปใน code ของฐานข้อมูล framework และ UI บทนี้จึงเปลี่ยนคำถามจาก “จะสร้าง model อย่างไร” เป็น “จะ วาง model ไว้ตรงไหน ใน app จริง เพื่อให้มันอยู่รอดและไม่ถูกปนเปื้อน”
หัวใจของบทนี้สั้นมาก: Domain-Driven DesignDomain-Driven Design (DDD)แนวทางพัฒนาซอฟต์แวร์ที่เน้นสร้าง domain model ซึ่งเข้าใจกฎและกระบวนการของธุรกิจอย่างลึกซึ้ง แล้วเขียนลงใน code จริง บัญญัติโดย Eric Evans (2003) แบ่งเป็น Strategic และ Tactical DesignStrategic Design ไม่บังคับ ให้คุณใช้สถาปัตยกรรมแบบใดแบบหนึ่ง แต่สถาปัตยกรรมที่คุณเลือก ต้องทำสิ่งเดียวให้ได้ — แยก domain logic ออกจาก technical concern อย่างเด็ดขาด ถ้าทำไม่ได้ codebase จะค่อยๆ เสื่อมกลับไปเป็น Big Ball of MudBig Ball of Mudระบบที่ปนเปกันยุ่งเหยิงไม่มีขอบเขตชัด ให้ลากเส้นล้อมไว้แล้วยอมรับสภาพ ไม่พยายามทำ model ละเอียดในนั้น และระวังไม่ให้ลามไป context อื่นStrategic Design เหมือนเดิม
แยก concern เพื่อปกป้องความถูกต้องของ model
หัวข้อที่มีชื่อว่า “แยก concern เพื่อปกป้องความถูกต้องของ model”code ใน app หนึ่งๆ มี concern หลายชนิดที่ เปลี่ยนด้วยอัตราต่างกันและด้วยเหตุผลต่างกัน — presentation (UI) เปลี่ยนบ่อยตาม design, persistence เปลี่ยนตามเทคโนโลยีฐานข้อมูล, ส่วน domain logic เปลี่ยนตามกฎธุรกิจ ถ้าทั้งสามปนกันอยู่ใน file เดียว การแก้ design ปุ่มอาจไปทำกฎธุรกิจพังโดยไม่ตั้งใจ
DDD ต้องการสถาปัตยกรรมที่ทำสองอย่าง:
- Separation of concerns — แยกความซับซ้อนทางเทคนิคออกจากความซับซ้อนของ domain ให้แต่ละส่วนเปลี่ยนได้โดยไม่กระทบกัน
- Abstraction จากรายละเอียดของ domain — เปิดให้โลกภายนอกเรียกใช้ผ่านชุด use case แบบ coarse-grained (หยาบในระดับธุรกิจ) แทนที่จะให้ client เข้าไปยุ่งกับ domain object โดยตรง
สถาปัตยกรรมในมุมมอง DDD มีหน้าที่เดียว: กัน domain model ไว้ตรงใจกลาง ไม่ให้รายละเอียดทางเทคนิคเข้ามาแตะต้อง ไม่ว่าจะเป็น Layered, Hexagonal, Onion หรือ Clean Architecture — ทุกแบบล้วนรับใช้เป้าหมายเดียวกันนี้
Layered Architecture: domain อยู่แกนกลาง
หัวข้อที่มีชื่อว่า “Layered Architecture: domain อยู่แกนกลาง”วิธีที่คลาสสิกที่สุดในการแยก concern คือการแบ่ง app เป็น ชั้น (layer) ตามความรับผิดชอบ Martin Fowler รวบรวมรูปแบบ Layered ArchitectureLayered Architectureการแยก code เป็นชั้น (UI, Application, Domain, Infrastructure) โดยรวม code ที่เกี่ยวกับ domain ไว้ชั้นเดียวและแยกออกจากชั้นอื่น เป้าหมายคือการแยกส่วน (isolation)Architecture ไว้ในหนังสือ Patterns of Enterprise Application Architecture และยังมีสถาปัตยกรรมอื่นที่เป้าหมายเดียวกัน เช่น Clean ArchitectureClean Architectureแนวคิดของ Robert C. Martin ที่หลักการตรงกับ Hexagonal/Onion — dependency ทั้งหมดชี้เข้าหาแกนกลางที่เป็นกฎธุรกิจ ทำให้ domain ไม่ผูกกับ frameworkArchitecture ของ Uncle Bob (Robert C. Martin), Hexagonal ArchitectureHexagonal Architectureสถาปัตยกรรม “Ports and Adapters” โดย Alistair Cockburn — วาง domain ไว้ตรงกลาง นิยาม port (interface) ให้ adapter ภายนอกมาเสียบ ทุก dependency ชี้เข้าหา domainArchitecture (Ports and Adapters) และ Onion ArchitectureOnion Architectureสถาปัตยกรรมโดย Jeffrey Palermo — code พึ่งพาชั้นที่อยู่ “ด้านในกว่า” ได้ แต่พึ่งพาชั้นนอกไม่ได้ มี domain เป็นแกนกลางสุด แนวคิดเดียวกับ HexagonalArchitecture
แต่ภาพของ DDD ต่างจาก layered architecture แบบเรียงชั้นทั่วไป — รูปที่ 8-1 วาดมันเป็น วงกลมซ้อนกัน โดยมี domain layer อยู่ใจกลาง

- Domain Layer (แกนกลาง) — บรรจุตรรกะธุรกิจทั้งหมด ไม่รู้จักและไม่ขึ้นกับเทคโนโลยีใดๆ เลย
- Application Layer (ห่อรอบ domain) — เปิด API ระดับหยาบApplication Serviceบริการที่ “ประสานงาน” use case — จัดการ transaction, ความปลอดภัย, การบันทึก, แปลง DTO — แต่ไม่มีกฎธุรกิจอยู่ในตัวมันเองTactical Design ที่แทน business use case โดยซ่อนรายละเอียดระดับล่างของ domain เอาไว้
- Infrastructure Layers (วงนอก) — รายละเอียดทางเทคนิคล้วนๆ: UI, ฐานข้อมูล, web service, การส่งอีเมล
จุดสำคัญคือ domain layer และ application layer ถูก isolate และปกป้อง จาก accidental complexity ของ client framework และ infrastructure ทั้งหมด
Dependency Inversion: ทุก dependency ชี้เข้าด้านใน
หัวข้อที่มีชื่อว่า “Dependency Inversion: ทุก dependency ชี้เข้าด้านใน”ทำไมวงในถึงไม่ถูกปนเปื้อน? เพราะกฎเหล็กข้อเดียว — dependency ทุกเส้นต้องชี้เข้าหาแกนกลาง domain layer ที่อยู่ใจกลางไม่พึ่งพาอะไรเลย จึงโฟกัสที่เรื่อง domain ได้แบบไร้สิ่งรบกวน ส่วน application layer พึ่งพาเฉพาะ domain layer เท่านั้น

แล้ว state ของ domain object ที่ต้องบันทึกลงฐานข้อมูลล่ะ? นี่คือจุดที่ Dependency Inversion เปล่งประกาย แทนที่ domain จะไปเรียกฐานข้อมูล (ซึ่งจะทำให้ domain ผูกติดกับเทคนิค) เรากลับทิศความสัมพันธ์เสียใหม่:
- Application layer เป็นฝ่ายนิยาม interface — เขียนด้วยภาษาและมุมมองของ application เอง ปลอดจากศัพท์เทคนิคของ framework ใดๆ เช่น interface ที่บอกว่า “ขอ hydrate และ persist domain object”
- Infrastructure layer เป็นฝ่าย implement interface นั้น — มาเสียบเข้ากับ PortPortinterface ที่ domain นิยามไว้เพื่อบอกว่า “ต้องการอะไร” เช่น CargoRepository โดยไม่สนว่าใครจะมาทำให้ — โลกภายนอกต้องปรับตัวเข้าหา ไม่ใช่กลับกันArchitecture ที่วงในเปิดไว้ในฐานะ AdapterAdapterตัวที่ “ทำให้” port เป็นจริง เช่น PostgresCargoRepository หรือ REST controller อยู่ชั้นนอกและพึ่งพา domain ทำให้สลับเทคโนโลยีได้โดยไม่แตะ domainArchitecture
เรื่อง transaction management, security, logging (cross-cutting concern) ก็จัดการด้วยวิธีเดียวกัน
ใน Cargo Shipping ฝั่ง domain เรานิยาม CargoRepositoryRepositoryตัวที่ทำให้เราเข้าถึง Aggregate ราวกับเป็น collection ในหน่วยความจำ ซ่อนรายละเอียดฐานข้อมูล มี1 repository ต่อ1 Aggregate RootTactical Design ในฐานะ interface ที่ อยู่ในวงใน — มันพูดภาษาธุรกิจล้วนๆ: find(trackingId), store(cargo) ไม่มีคำว่า SQL หรือ table อยู่เลย
ส่วน SqlCargoRepository ที่รู้จัก connection string และตาราง cargo ถูกวางไว้ในวง infrastructure แล้วมา implement interface นั้น ผลคือเราสลับจาก SQL ไป NoSQL ได้โดย ไม่ต้องแตะ domain layer แม้แต่บรรทัดเดียว
// interface นี้อยู่ "วงใน" — domain เป็นฝ่ายกำหนดว่าตัวเองต้องการอะไร// โลกภายนอกต้องปรับตัวมาทำตาม ไม่ใช่กลับกันinterface CargoRepository { find(trackingId: TrackingId): Cargo | null; store(cargo: Cargo): void;}สามชั้นทำงานร่วมกันอย่างไร
หัวข้อที่มีชื่อว่า “สามชั้นทำงานร่วมกันอย่างไร”Domain Layer — หัวใจที่ไม่รู้จักโลกภายนอก
หัวข้อที่มีชื่อว่า “Domain Layer — หัวใจที่ไม่รู้จักโลกภายนอก”ตามที่เราเรียนใน Model-Driven Design domain modelDomain ModelModel ของ Domain ที่ฝังกฎและกระบวนการทางธุรกิจไว้ใน code จริง หัวใจของ DDD คือการพัฒนา domain model ที่ “เข้าใจธุรกิจอย่างลึกซึ้ง”Strategic Design คือภาพนามธรรมของ problem domain ที่สร้างขึ้นเพื่อตอบ business use case domain layer ที่บรรจุ model นี้ ไม่พึ่งพาอะไรเลย และไม่สนใจว่า client เป็นใครหรือข้อมูลถูกเก็บที่ไหน
Application Service Layer — façade ของ use case
หัวข้อที่มีชื่อว่า “Application Service Layer — façade ของ use case”application service layerApplication Serviceบริการที่ “ประสานงาน” use case — จัดการ transaction, ความปลอดภัย, การบันทึก, แปลง DTO — แต่ไม่มีกฎธุรกิจอยู่ในตัวมันเองTactical Design แทน use case และกระบวนการธุรกิจ (business process) ของ app มันทำงานที่ระดับ abstraction สูงกว่า domain object — เปิดบริการระดับหยาบในเชิงธุรกิจ และซ่อนรายละเอียดของ domain ไว้หลัง façade บอกว่า “ระบบทำอะไรได้” แต่ไม่บอก “ทำอย่างไร” ผลคือเราพัฒนา model ด้านในต่อไปได้โดยไม่กระทบ client
มุมมองที่ทรงพลังมากคือ: application layer ทำตัวเป็น Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design — client ใหม่ทุกตัวต้องปรับตัวเข้าหา contract (API) ของ app และต้องแปลง output เอง ไม่ใช่ให้ app ไปปรับตามใจ client วิธีนี้ทำให้ domain layer “บริสุทธิ์” ไม่ถูกรายละเอียดทางเทคนิคภายนอกมาแปดเปื้อน
Infrastructure Layers — รายละเอียดทางเทคนิคล้วนๆ
หัวข้อที่มีชื่อว่า “Infrastructure Layers — รายละเอียดทางเทคนิคล้วนๆ”infrastructure คือสิ่งที่ทำให้ app “ทำงานได้จริง”: การเปิดให้คนเข้าถึงผ่าน UI หรือให้ระบบอื่นเข้าถึงผ่าน web service/message endpoint, การ implement การจัดเก็บ state, logging, security, notification และการ integrate กับ bounded context อื่น ทั้งหมดนี้เป็น “รายละเอียดภายนอก” ที่ไม่ควรกระทบ use case และ domain logic โดยตรง
สื่อสารข้ามชั้นโดยไม่ปล่อย domain object รั่ว
หัวข้อที่มีชื่อว่า “สื่อสารข้ามชั้นโดยไม่ปล่อย domain object รั่ว”เมื่อข้อมูลวิ่งข้ามขอบเขตของชั้น มีกฎสองข้อ:
- อย่าส่ง domain object ออกไปข้ามขอบเขต — เพราะนั่นคือการเปิดเผยโครงสร้างภายในของ model ให้โลกภายนอก
- อย่าปล่อยข้อมูลดิบหรือ user input ตรงเข้า domain layer — ต้องผ่านการแปลงก่อนเสมอ
แทนที่จะส่ง domain object เราใช้ DTO (Data Transfer Object), presentation model และ application event object เป็นตัวกลางสื่อสารการเปลี่ยนแปลง ชั้นที่อยู่สูงกว่าต้องปรับตัวเข้าหา message type ของชั้นที่ต่ำกว่า ทำให้วงในยังคง isolate และ loosely coupled
class BookCargoService { bookCargo(request: BookCargoRequest): BookingResult { // 1) แปลง input ดิบ -> Value Object ที่ domain เข้าใจ const spec = new RouteSpecification(request.origin, request.destination, request.deadline); const cargo = Cargo.book(this.trackingIdFactory.next(), spec);
this.cargoRepository.store(cargo);
// 2) แปลง domain object -> DTO ก่อนส่งกลับ ไม่ปล่อย Cargo รั่วออกไป return new BookingResult(cargo.trackingId.value, cargo.routingStatus); }}ทดสอบแยกส่วนได้ เพราะแยก concern แล้ว
หัวข้อที่มีชื่อว่า “ทดสอบแยกส่วนได้ เพราะแยก concern แล้ว”ผลพลอยได้ที่ยอดเยี่ยมของการแยก concern คือ ทดสอบได้ง่ายขึ้นมาก เมื่อ domain logic ไม่ผูกกับ code เชิงเทคนิค (UI, persistence framework) เราทดสอบ domain และ application logic แบบ isolate ได้
- Domain layer — ใช้ unit test ยืนยันตรรกะล้วนๆ ได้เลย ไม่ต้องมีฐานข้อมูล
- Application layer — ใช้ mock/stub แทน external resource เพื่อยืนยันว่ามันประสานงาน (coordinate) ระหว่าง domain กับ infrastructure ถูกต้อง
- Infrastructure layer — เน้น integration test และ end-to-end test แทน เพราะมันคือจุดที่แตะของจริง
อย่า share data schema ข้าม Bounded Context
หัวข้อที่มีชื่อว่า “อย่า share data schema ข้าม Bounded Context”การแยก concern ต้องลามไปถึง persistence ด้วย วิธี integrate ที่ดูง่ายแต่อันตรายคือ การให้หลาย app ใช้ฐานข้อมูลและ schema ชุดเดียวกัน
การ share schema เปิดช่องให้ client หลบเลี่ยงกลไกป้องกันของ Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ได้โดยง่าย — มันเขียนทับ state ของ domain object ได้โดยตรง ข้าม domain logic ที่ควรเป็นผู้บังคับ InvariantInvariantกฎทางธุรกิจที่ “ต้องเป็นจริงเสมอ” ภายใน Aggregate เช่น ยอดรวมคำสั่งซื้อต้องเท่ากับผลรวมของรายการสินค้า — ต้องถูกต้องภายใน transaction เดียวTactical Design ผลที่ตามมาคือข้อมูลที่ละเมิดกฎธุรกิจ และเส้นแบ่งของ model ที่พร่าเลือนจนกลายเป็น BBoM
ทางที่ถูกคือ favor application/bounded-context database มากกว่า integration database — bounded context สองตัวอาจใช้เครื่องฐานข้อมูลร่วมกันได้ แต่ schema ต้องแยกกัน (ในอุดมคติคือต่างฐานข้อมูลไปเลย) เหมือนที่เราลากเส้น context boundary ใน model เราต้องลากเส้นเดียวกันกับ persistence model ด้วย เพื่อบังคับให้ทุกคน integrate ผ่าน application service layer ที่นิยามไว้ชัดเจน
สถาปัตยกรรมของ app ≠ สถาปัตยกรรมของ Bounded Context
หัวข้อที่มีชื่อว่า “สถาปัตยกรรมของ app ≠ สถาปัตยกรรมของ Bounded Context”app หนึ่งตัวอาจประกอบด้วย bounded context มากกว่าหนึ่ง ตัว และสถาปัตยกรรมก็ใช้กับ “app” และ “bounded context” คนละแบบกัน
graph TD UI["Presentation Layer<br/>(มี application layer ของตัวเองเพื่อประสาน context)"] UI --> BCA["Bounded Context A<br/>Domain Model Pattern"] UI --> BCB["Bounded Context B<br/>Transaction Script"] UI --> BCC["Bounded Context C<br/>Table Module"]
สังเกตว่าแต่ละ bounded context เลือกวิธีแทน domain logic ของตัวเองได้อิสระ — context ที่ตรรกะรวยอาจใช้ Domain Model patternDomain ModelModel ของ Domain ที่ฝังกฎและกระบวนการทางธุรกิจไว้ใน code จริง หัวใจของ DDD คือการพัฒนา domain model ที่ “เข้าใจธุรกิจอย่างลึกซึ้ง”Strategic Design ส่วน context ที่งานง่ายๆ อาจใช้ Transaction ScriptTransaction Scriptรูปแบบที่จัดตรรกะธุรกิจเป็นชุดคำสั่งตามขั้นตอน (procedural) แทนการกระจายลงในอ็อบเจ็กต์ เหมาะกับงานง่าย ๆ แต่จะรกเมื่อ domain ซับซ้อนขึ้นArchitecture หรือ Table Module ก็ได้
มีอีกสำนักคิด (Udi Dahan’s business component) ที่เชื่อว่าขอบเขตของ bounded context ควรขยายไปถึง presentation layer ด้วย — คือให้แต่ละ context เป็นเจ้าของส่วน UI เฉพาะของมันเอง โดยมี infrastructure คอยจัดการการสื่อสารและ correlation ID
ไม่จำเป็น ต้องใช้สถาปัตยกรรมหรือ data store แบบเดียวกันทั่วทุก bounded context — แต่ ภายใน bounded context เดียว ควรยึดวิธีแทน domain logic แบบเดียวให้สม่ำเสมอ
Application Logic ≠ Domain Logic
หัวข้อที่มีชื่อว่า “Application Logic ≠ Domain Logic”นี่คือเส้นแบ่งที่คนสับสนบ่อยที่สุด รูปที่ 8-9 แยกให้เห็นชัด

Application logic = ขั้นตอน workflow เพื่อทำ use case ให้สำเร็จ เช่น hydrate domain object จากฐานข้อมูล, map user input เป็น object ที่ domain เข้าใจ, มอบหมาย (delegate) ให้ domain ตัดสินใจ, แล้วแจ้งระบบอื่นผ่าน messaging มันเป็นเรื่องของ coordination และ orchestration ผ่านการ delegate — application service เอง “ไม่ลงมือทำงานจริง” แต่รู้ว่า “ต้องคุยกับใคร” เพื่อให้งานสำเร็จ จึงควรเป็น code แบบ procedural ที่ บาง (thin)
Domain logic = กฎ แนวคิด ข้อมูล และ flow ของ domain ล้วนๆ ปลอดจากรายละเอียดเทคนิครวมถึง persistence
ลองดู use case “ใช้คูปองกับตะกร้า” ตามรูป 8-9:
- presentation layer (เช่น ASP.NET MVC) แปลง HTTP request เป็นรูปแบบที่ application service ต้องการ แล้วเรียก service method — นี่คือ application logic
- application service delegate ไป persistence เพื่อดึง object
Couponมา แล้วเช็กว่ายังใช้ได้ไหม — coordination = application logic - ถ้าใช้ได้ มันดึง
Basketมา แล้ว ส่ง basket ให้ coupon เป็นฝ่ายคำนวณส่วนลดเอง — การคำนวณส่วนลดคือ domain logic อยู่ใน domain object - บันทึกการเปลี่ยนแปลง แล้ว publish eventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ว่า coupon ถูก redeem — กลับมาเป็น application logic
เห็นไหมว่า service เป็นเพียง “ผู้ควบคุมวง” ส่วนการตัดสินใจเชิงธุรกิจถูกผลักเข้าไปไว้ใน domain object เสมอ
ถ้าคุณพบเงื่อนไข if ที่เป็นกฎธุรกิจ (เช่น “ส่วนลดเกิน 50% ต้องให้ผู้จัดการอนุมัติ”) อยู่ใน application service — นั่นคือสัญญาณว่าตรรกะกำลังรั่วไหลออกจาก domain ปลายทางคือ Anemic Domain ModelAnemic Domain Modelanti-pattern ที่อ็อบเจ็กต์มีแต่ getter/setter ไร้พฤติกรรม ตรรกะถูกดึงไปไว้ใน service ทั้งหมด — จ่ายค่า domain model แต่ไม่ได้ประโยชน์ของมันเลยTactical Design ที่ domain object เหลือแต่ getter/setter ส่วนกฎทั้งหมดกระจัดกระจายอยู่ใน service
Application Service แทน Use Case ไม่ใช่ CRUD
หัวข้อที่มีชื่อว่า “Application Service แทน Use Case ไม่ใช่ CRUD”เพราะ application service เปิดเผย ความสามารถของระบบ มันจึงไม่ควรเปลี่ยนตาม client — client ต่างหากที่ต้องปรับตัวเข้าหา contract ของ service capability ของระบบควรเปลี่ยน ก็ต่อเมื่อ business use case เปลี่ยน เท่านั้น ซึ่งเกิดด้วยอัตราและเหตุผลต่างจาก domain logic ที่อยู่เบื้องหลัง — นี่เองที่ปกป้อง client จากการเปลี่ยนแปลงบ่อยๆ ของ domain
ตัวอย่าง: use case “ประเมินความเสี่ยงทุจริตของออเดอร์” — domain logic ภายในอาจเปลี่ยนวิธีคำนวณคะแนนไปเรื่อยๆ แต่ application service ที่เป็น “หน้าตา” ของ use case นั้นแทบไม่เปลี่ยน จะเปลี่ยนก็เฉพาะตอนแก้ contract เพื่อให้ข้อมูลเพิ่ม
application service จึงควรตั้งชื่อให้ เผยเจตนาของผู้ใช้ ไม่ใช่ตั้งตาม CRUD — Behavior-Driven Design (BDD) ช่วยให้เราจับกระบวนการธุรกิจ (business process) ของระบบ แล้วนำภาษาจาก BDD specification มาตั้งชื่อ use case เหมือนที่เราใช้ Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design ใน code ของ domain layer
method ของ application service เป็นเครื่องชี้วัดที่ดี: ถ้าทุก use case เป็นแค่ add/update/delete ข้อมูลเฉยๆ แสดงว่า domain แทบไม่มีตรรกะจริง — ใช้ Transaction ScriptTransaction Scriptรูปแบบที่จัดตรรกะธุรกิจเป็นชุดคำสั่งตามขั้นตอน (procedural) แทนการกระจายลงในอ็อบเจ็กต์ เหมาะกับงานง่าย ๆ แต่จะรกเมื่อ domain ซับซ้อนขึ้นArchitecture ก็พอ ไม่ต้องลงทุนทำ rich domain model แต่ถ้า service และกระบวนการธุรกิจ (business process) เต็มไปด้วยภาษาเชิงธุรกิจที่รวย นั่นคือสัญญาณว่าควรใช้ Domain Model pattern
Domain Reporting: Read Model แยกจาก Transactional Model
หัวข้อที่มีชื่อว่า “Domain Reporting: Read Model แยกจาก Transactional Model”นอกจากประสานงาน application service ยังต้อง รายงาน state ของ domain ออกมาเป็น view แต่เราไม่อยากเปิดโครงสร้างภายในของ model จึงแปลง domain object เป็น presentation/view model ที่ให้เฉพาะมุมที่ต้องการ

ปัญหาคือ บาง view ต้องการข้อมูลที่ ข้ามหลาย AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design การ hydrate domain object ที่รวยทั้งก้อนเพียงเพื่อแสดงข้อมูลเพียงบางส่วนบนหน้าจอนั้นสิ้นเปลืองโดยใช่เหตุ ทางที่ดีกว่าคือให้ application service ดึง view ออกมา ตรงจาก data source โดยไม่ต้องประกอบ object graph ขนาดใหญ่
แต่การให้ทั้ง read และ write ใช้ model เดียวกันมีข้อเสีย — ความต้องการของ view อาจบีบให้โครงสร้างของ domain object ต้องเปลี่ยนตามความต้องการของ presentation เพื่อกันเรื่องนี้ เราเก็บข้อมูลสำหรับอ่านไว้ใน schema แยกที่เหมาะกับการ query โดยอาจอาศัยการบันทึก eventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ที่เกิดใน model มาเป็นฐานของรายงาน
รูปแบบที่แยก “ฝั่งเขียน” (transactional model) ออกจาก “ฝั่งอ่าน” (read model) อย่างชัดเจนนี้เรียกว่า CQRSCQRSCommand Query Responsibility Segregation — แยก model สำหรับ “เขียน/แก้ไข” (command) ออกจาก model สำหรับ “อ่าน” (query) เหมาะกับ domain ซับซ้อน แต่เพิ่มความซับซ้อน ใช้อย่างระมัดระวังArchitecture (Command Query Responsibility Segregation) — เราจะลงลึกในบทหลัง
Application Clients: service ต้องไม่รู้จัก client
หัวข้อที่มีชื่อว่า “Application Clients: service ต้องไม่รู้จัก client”หน้าที่ของ client คือ “เปิดเผยความสามารถของระบบสู่ผู้ใช้” — บาง app มี UI, บาง app เปิดเป็น REST/web service ไม่ว่าแบบไหน หลักการเดียวคือ service ต้องไม่รู้และไม่สนว่าใครเรียกใช้มัน application service ไม่ควรดัดตัวเองให้เข้ากับ client แต่ควรเปิด use case แล้วบังคับให้ client ปรับตัวเข้าหา API

จริงๆ แล้วคุณ สร้างระบบโดยไม่มี application service layer ก็ได้ — ปล่อยให้ client ทำงานทั้งหมดแทน แต่การสร้างชุด service ที่ชัดเจนทำให้คุณ model use case ออกมาเป็นรูปธรรม และแยกมันออกจากความต้องการของ presentation ทำให้โฟกัสที่พฤติกรรมของระบบและแยก domain logic ออกจาก concern อื่นได้สะอาด
bounded context หลายตัวประกอบกันเป็นระบบใหญ่ผ่าน technical infrastructure (เช่น messaging) และเมื่อกระบวนการธุรกิจ (business process) พาดผ่าน หลาย bounded context เราใช้ Process Manager มาประสานงาน — มันคล้าย application service คือ stateless (ยกเว้น state ที่ใช้ติดตามความคืบหน้าของ task) และ delegate งานกลับไปให้แต่ละ app ทำ
ระบบขนส่งของเรามี client หลายแบบที่เรียกใช้ความสามารถเดียวกัน: เจ้าหน้าที่หน้าเคาน์เตอร์จองผ่านเว็บ UI, ระบบของท่าเรือยิง message เข้ามาแจ้ง Handling EventHandling Eventการบันทึกการจัดการสินค้าจริง (LOAD, UNLOAD, RECEIVE, CLAIM) เป็น Aggregate “แยกต่างหาก” จาก Cargo เพราะมีปริมาณมากและต้องประมวลผลแบบ asyncTactical Design เมื่อโหลด/ขนถ่ายตู้, และพาร์ทเนอร์เรียกผ่าน web service ทั้งสามเข้าทาง BookCargoService / HandlingEventService ชุดเดียวกัน — ถ้าวันหน้าเพิ่ม mobile app เข้ามา เราแค่ให้มันปรับตัวเข้าหา API เดิม ไม่ต้องแก้ domain เลย
สรุปประเด็นสำคัญ (The Salient Points)
หัวข้อที่มีชื่อว่า “สรุปประเด็นสำคัญ (The Salient Points)”- DDD ไม่บังคับ สถาปัตยกรรมแบบใด — ขอเพียงแยก technical concern ออกจาก business concern ให้ขาดจากกัน
- แยก concern ด้วยการ layer เพื่อ isolate ความซับซ้อนทางธุรกิจออกจากความซับซ้อนทางเทคนิค
- ชั้นนอกพึ่งชั้นใน ส่วนชั้นในเปิด interface ให้ชั้นนอกมา implement — dependency inversion นี้คือกลไกที่ปกป้อง domain
- domain layer อยู่ใจกลาง ถูก isolate จากเทคนิคโดยมี application layer ห่อหุ้มไว้
- application service เปิดความสามารถของระบบโดยยก domain logic ขึ้นไปสู่ระดับ abstraction ที่สูงกว่า, อิงกับ use case, เป็น client ของ domain และ delegate งานให้ domain
- application service ต้อง ไม่รู้จัก client — client เป็นฝ่ายปรับตัวเข้าหา API ทำให้รองรับ client ได้หลายแบบ
- application service layer คือการ implement ขอบเขตของ bounded context อย่างเป็นรูปธรรม
เจาะลึกแนวคิดในบทนี้ต่อได้ที่คลังอ้างอิง DevIQ:
- Anti-Corruption Layer — ขยายความมุมมองที่ว่า application layer ทำตัวเป็นชั้นป้องกันไม่ให้รายละเอียดของ client ภายนอกไหลย้อนเข้ามาปนเปื้อน domain
- Repository — รูปแบบที่ใช้อธิบายตัวอย่าง
CargoRepositoryในบทนี้ ซึ่งเป็นตัวอย่างคลาสสิกของ dependency inversion ระหว่าง domain กับ infrastructure - CQRS — ขยายความส่วน “Domain Reporting” ที่แยก read model ออกจาก transactional model
- Anemic Domain Model — อธิบายผลลัพธ์ที่เกิดขึ้นเมื่อกฎธุรกิจรั่วไหลจาก domain layer เข้าไปอยู่ใน application service
เช็กความเข้าใจ — บทที่ 8
ข้อ 1 / 4ตามหนังสือ DDD เรียกร้องอะไรจากสถาปัตยกรรมของ app?