Architecture & Advanced
DDD เข้ากันได้ดีกับสถาปัตยกรรมที่วาง Domain ModelDomain ModelModel ของ Domain ที่ฝังกฎและกระบวนการทางธุรกิจไว้ใน code จริง หัวใจของ DDD คือการพัฒนา domain model ที่ “เข้าใจธุรกิจอย่างลึกซึ้ง”Strategic Design ไว้ตรงกลางและกัน dependency ภายนอกออกไป รวมถึงรูปแบบขั้นสูงอย่าง CQRSCQRSCommand Query Responsibility Segregation — แยก model สำหรับ “เขียน/แก้ไข” (command) ออกจาก model สำหรับ “อ่าน” (query) เหมาะกับ domain ซับซ้อน แต่เพิ่มความซับซ้อน ใช้อย่างระมัดระวังArchitecture และ Event SourcingEvent Sourcingเก็บทุกการเปลี่ยนสถานะเป็น event ที่เปลี่ยนไม่ได้ แล้วสร้างสถานะปัจจุบันด้วยการเล่น event ซ้ำ มัก “ต้องใช้คู่กับ CQRS” (Greg Young) มี event stream ต่อ1 AggregateArchitecture — แต่ทุกอย่างมี “ต้นทุนความซับซ้อน” ที่ต้องชั่งน้ำหนัก
จาก Layered สู่ Hexagonal / Onion / Clean
หัวข้อที่มีชื่อว่า “จาก Layered สู่ Hexagonal / Onion / Clean”Evans เสนอ Layered ArchitectureLayered Architectureการแยก code เป็นชั้น (UI, Application, Domain, Infrastructure) โดยรวม code ที่เกี่ยวกับ domain ไว้ชั้นเดียวและแยกออกจากชั้นอื่น เป้าหมายคือการแยกส่วน (isolation)Architecture เดิม โดยรวม code domain ไว้ชั้นเดียวและแยกออกจาก UI/Application/Infrastructure เป้าหมายคือ “การแยกส่วน” ต่อมาเขายอมรับว่า Hexagonal ArchitectureHexagonal Architectureสถาปัตยกรรม “Ports and Adapters” โดย Alistair Cockburn — วาง domain ไว้ตรงกลาง นิยาม port (interface) ให้ adapter ภายนอกมาเสียบ ทุก dependency ชี้เข้าหา domainArchitecture อธิบายสิ่งที่เราทำได้ดีกว่า ทั้ง Hexagonal (Alistair Cockburn), Onion ArchitectureOnion Architectureสถาปัตยกรรมโดย Jeffrey Palermo — code พึ่งพาชั้นที่อยู่ “ด้านในกว่า” ได้ แต่พึ่งพาชั้นนอกไม่ได้ มี domain เป็นแกนกลางสุด แนวคิดเดียวกับ HexagonalArchitecture (Jeffrey Palermo), และ Clean ArchitectureClean Architectureแนวคิดของ Robert C. Martin ที่หลักการตรงกับ Hexagonal/Onion — dependency ทั้งหมดชี้เข้าหาแกนกลางที่เป็นกฎธุรกิจ ทำให้ domain ไม่ผูกกับ frameworkArchitecture (Robert C. Martin) ล้วนมีแก่นเดียวกัน:
- Domain ModelDomain ModelModel ของ Domain ที่ฝังกฎและกระบวนการทางธุรกิจไว้ใน code จริง หัวใจของ DDD คือการพัฒนา domain model ที่ “เข้าใจธุรกิจอย่างลึกซึ้ง”Strategic Design อยู่ตรงกลาง ไม่ขึ้นกับฐานข้อมูล framework หรือ UI
- domain นิยาม PortPortinterface ที่ domain นิยามไว้เพื่อบอกว่า “ต้องการอะไร” เช่น CargoRepository โดยไม่สนว่าใครจะมาทำให้ — โลกภายนอกต้องปรับตัวเข้าหา ไม่ใช่กลับกันArchitecture (interface) บอกว่า “ต้องการอะไร” เช่น OrderRepository
- AdapterAdapterตัวที่ “ทำให้” port เป็นจริง เช่น PostgresCargoRepository หรือ REST controller อยู่ชั้นนอกและพึ่งพา domain ทำให้สลับเทคโนโลยีได้โดยไม่แตะ domainArchitecture ภายนอก (Postgres, REST, คิว) มา “เสียบ” port โดยพึ่งพา domain
- dependency ทุกตัวชี้เข้าหา domain — นี่คือสิ่งที่ทำให้สลับเทคโนโลยีได้โดยไม่แตะกฎธุรกิจ
Hexagonal Architecture — domain อยู่แกนกลาง
ports & adaptersจำ RepositoryRepositoryตัวที่ทำให้เราเข้าถึง Aggregate ราวกับเป็น collection ในหน่วยความจำ ซ่อนรายละเอียดฐานข้อมูล มี1 repository ต่อ1 Aggregate RootTactical Design จาก Stage 3 ได้ไหม? interface ของมันคือ PortPortinterface ที่ domain นิยามไว้เพื่อบอกว่า “ต้องการอะไร” เช่น CargoRepository โดยไม่สนว่าใครจะมาทำให้ — โลกภายนอกต้องปรับตัวเข้าหา ไม่ใช่กลับกันArchitecture ที่ domain นิยาม ส่วน implementation ที่ใช้ Drizzle/Postgres คือ AdapterAdapterตัวที่ “ทำให้” port เป็นจริง เช่น PostgresCargoRepository หรือ REST controller อยู่ชั้นนอกและพึ่งพา domain ทำให้สลับเทคโนโลยีได้โดยไม่แตะ domainArchitecture ที่ชั้น infrastructure ทำให้ domain ไม่รู้จักฐานข้อมูลเลย — เป็นรูปธรรมของหลักการ “dependency ชี้เข้าใน”
CQRS — แยกฝั่งเขียนออกจากฝั่งอ่าน
หัวข้อที่มีชื่อว่า “CQRS — แยกฝั่งเขียนออกจากฝั่งอ่าน”CQRSCQRSCommand Query Responsibility Segregation — แยก model สำหรับ “เขียน/แก้ไข” (command) ออกจาก model สำหรับ “อ่าน” (query) เหมาะกับ domain ซับซ้อน แต่เพิ่มความซับซ้อน ใช้อย่างระมัดระวังArchitecture (Greg Young) เสนอว่าเราสามารถใช้ model คนละตัวสำหรับ “เขียน/แก้ไข” (command) กับ “อ่าน” (query) ได้ เพราะในหลายกรณี โดยเฉพาะ domain ซับซ้อน การใช้ model เดียวทำทั้งสองอย่างทำให้ model ซับซ้อนโดยไม่จำเป็น Fowler อธิบายว่า CQRS เหมาะกับ domain แบบเดียวกับที่ได้ประโยชน์จาก DDD
“สำหรับระบบส่วนใหญ่ CQRS เพิ่มความซับซ้อนที่มีความเสี่ยง” Fowler เคยเห็นหลายกรณีที่มันฉุดประสิทธิภาพการทำงานของทีมและเพิ่มความเสี่ยงเกินควร — แม้ในมือทีมที่เก่ง อย่าใช้ CQRS เป็นค่าตั้งต้น แต่ใช้เฉพาะส่วนที่คุ้มจริง
Event Sourcing — เก็บประวัติทุกการเปลี่ยนแปลง
หัวข้อที่มีชื่อว่า “Event Sourcing — เก็บประวัติทุกการเปลี่ยนแปลง”Event SourcingEvent Sourcingเก็บทุกการเปลี่ยนสถานะเป็น event ที่เปลี่ยนไม่ได้ แล้วสร้างสถานะปัจจุบันด้วยการเล่น event ซ้ำ มัก “ต้องใช้คู่กับ CQRS” (Greg Young) มี event stream ต่อ1 AggregateArchitecture เก็บทุกการเปลี่ยนสถานะเป็น event ที่เปลี่ยนไม่ได้ แล้วสร้างสถานะปัจจุบันด้วยการ “เล่น event ซ้ำ” จุดเชื่อมกับ DDD คือ “มี1 event stream ต่อหนึ่ง AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design” — Aggregate จึงเป็นหน่วยธรรมชาติของสายเหตุการณ์ Greg Young สรุปความสัมพันธ์ไว้ว่า: ใช้ CQRS โดยไม่มี Event Sourcing ได้ แต่ถ้าใช้ Event Sourcing ก็ “ต้อง” ใช้ CQRS
CQRS + Event Sourcing — การไหลของคำสั่งและเหตุการณ์
event flowแทนที่จะเก็บ “สถานะปัจจุบัน” ของ Cargo เราเก็บลำดับเหตุการณ์: CargoBooked → CargoRouted → CargoLoaded → CargoUnloaded → CargoClaimed สถานะ ณ ปัจจุบันคือผลของการเล่นเหตุการณ์เหล่านี้ตามลำดับ ข้อดีคือได้ประวัติครบถ้วนเพื่อตรวจสอบย้อนหลัง แต่แลกมาด้วยความซับซ้อนที่สูงขึ้นมาก
Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ทำงาน “ภายใน” Bounded Context เดียว (in-process) ส่วน Integration EventIntegration Eventevent ที่สื่อสาร “ข้าม” Bounded Context หรือข้าม microservice ผ่าน event bus — ต่างจาก Domain Event ที่ทำงานภายใน context เดียว (in-process)Architecture สื่อสาร “ข้าม” context/microservice ผ่าน event bus อย่าสับสนสองอย่างนี้ — ขอบเขตต่างกัน
เจาะลึกแนวคิดในบทนี้ต่อได้ที่คลังอ้างอิง DevIQ:
- CQRS — แยกฝั่งเขียน/อ่านตามที่บทนี้อธิบาย พร้อมข้อควรระวังเรื่องความซับซ้อนที่เพิ่มขึ้น
- Domain Events — กลไกเบื้องหลัง event ที่ Event Sourcing ใช้เก็บประวัติการเปลี่ยนแปลง และความต่างจาก integration event
- Repository — ตัวอย่าง Port/Adapter รูปธรรมที่บทนี้ใช้อธิบาย Hexagonal Architecture
- Aggregate — หน่วยที่ผูกกับ event stream หนึ่งเส้นใน Event Sourcing ตามที่ Greg Young อธิบาย
เช็กความเข้าใจ — Stage 4
ข้อ 1 / 4หลักการร่วมของ Hexagonal / Onion / Clean Architecture คือ?