ข้าม​ไป​ยัง​เนื้อหา

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 — แต่​ทุก​อย่าง​มี “ต้นทุน​ความ​ซับซ้อน” ที่​ต้อง​ชั่ง​น้ำหนัก

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
Domain ModelAggregates · EntitiesValue ObjectsPort: RepositoryPort: PaymentPort: NotifierREST Controller(driving adapter)Postgres Repository(driven adapter)Event Publisher(driven adapter)
ทุก dependency ชี้เข้าหา domain ที่แกนกลาง · domain นิยาม Port (interface) ส่วน Adapter ภายนอกมาเสียบ — สลับฐานข้อมูล/UI ได้โดยไม่แตะกฎธุรกิจ
Hexagonal Architecture — domain อยู่แกนกลาง ล้อมด้วย ports และ adapters
ทำไม Repository ถึง​สำคัญ​ตรง​นี้

จำ 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 ชี้​เข้า​ใน”


CQRSCQRSCommand Query Responsibility Segregation — แยก model สำหรับ “เขียน/แก้ไข” (command) ออก​จาก model สำหรับ “อ่าน” (query) เหมาะ​กับ domain ซับซ้อน แต่​เพิ่ม​ความ​ซับซ้อน ใช้​อย่าง​ระมัดระวังArchitecture (Greg Young) เสนอ​ว่า​เรา​สามารถ​ใช้ model คนละ​ตัว​สำหรับ “เขียน/แก้ไข” (command) กับ “อ่าน” (query) ได้ เพราะ​ใน​หลาย​กรณี โดย​เฉพาะ domain ซับซ้อน การ​ใช้ model เดียว​ทำ​ทั้ง​สอง​อย่าง​ทำให้ model ซับซ้อน​โดย​ไม่​จำเป็น Fowler อธิบาย​ว่า CQRS เหมาะ​กับ domain แบบ​เดียว​กับ​ที่​ได้​ประโยชน์​จาก DDD

คำ​เตือน​สำคัญ​จาก Fowler

“สำหรับ​ระบบ​ส่วน​ใหญ่ CQRS เพิ่ม​ความ​ซับซ้อน​ที่​มี​ความ​เสี่ยง” Fowler เคย​เห็น​หลาย​กรณี​ที่​มัน​ฉุด​ประสิทธิภาพ​การ​ทำงาน​ของ​ทีม​และ​เพิ่ม​ความ​เสี่ยง​เกิน​ควร — แม้​ใน​มือ​ทีม​ที่​เก่ง อย่า​ใช้ CQRS เป็น​ค่า​ตั้งต้น แต่​ใช้​เฉพาะ​ส่วน​ที่​คุ้ม​จริง

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
ฝั่งเขียน · COMMANDฝั่งอ่าน · QUERYCommandเช่น BookCargoAggregate(write model)Domain EventCargoBookedEvent Storeappend-only logProjection/ handlerRead Model(query model)Query / UI
ฝั่งเขียน (บน): Command → Aggregate → Domain Event → ต่อท้ายลง Event Store · ฝั่งอ่าน (ล่าง): project เหตุการณ์เป็น Read Model สำหรับ query — สอง model แยกกันคือหัวใจของ CQRS
CQRS + Event Sourcing — การไหลของคำสั่งและเหตุการณ์ในระบบ
🚢 Cargo ใน​มุม Event Sourcing

แทนที่​จะ​เก็บ “สถานะ​ปัจจุบัน” ของ Cargo เรา​เก็บ​ลำดับ​เหตุการณ์: CargoBookedCargoRoutedCargoLoadedCargoUnloadedCargoClaimed สถานะ ณ ปัจจุบัน​คือ​ผล​ของ​การ​เล่น​เหตุการณ์​เหล่า​นี้​ตาม​ลำดับ ข้อดี​คือ​ได้​ประวัติ​ครบถ้วน​เพื่อ​ตรวจสอบ​ย้อนหลัง แต่​แลก​มา​ด้วย​ความ​ซับซ้อน​ที่​สูง​ขึ้น​มาก

Domain Event vs Integration Event

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

เจาะ​ลึก​แนวคิด​ใน​บท​นี้​ต่อ​ได้ที่​คลัง​อ้างอิง 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 คือ?