รูปแบบการ implement Domain Model
หัวใจของ Domain-Driven DesignDomain-Driven Design (DDD)แนวทางพัฒนาซอฟต์แวร์ที่เน้นสร้าง domain model ซึ่งเข้าใจกฎและกระบวนการของธุรกิจอย่างลึกซึ้ง แล้วเขียนลงใน code จริง บัญญัติโดย Eric Evans (2003) แบ่งเป็น Strategic และ Tactical DesignStrategic Design คือการ จัดการความซับซ้อน โดยวาง โมเดลของ domainDomain ModelModel ของ Domain ที่ฝังกฎและกระบวนการทางธุรกิจไว้ใน code จริง หัวใจของ DDD คือการพัฒนา domain model ที่ “เข้าใจธุรกิจอย่างลึกซึ้ง”Strategic Design ไว้ตรงกลางของซอฟต์แวร์เพื่อรองรับกระบวนการธุรกิจ (business process) ของ app แต่เมื่อถึงเวลาเขียน model นั้น “ลงใน code จริง” เรามีรูปแบบ (pattern) ให้เลือกหลายแบบ และ — นี่คือประเด็นสำคัญ — ไม่มีรูปแบบใดเป็นกระสุนเงิน (silver bullet) ที่ดีที่สุดเสมอไป
ในบทที่ 3 เราเห็นแล้วว่า app ขนาดใหญ่หนึ่งตัวอาจมีหลาย model อยู่พร้อมกัน และ model เหล่านั้น ไม่ได้ซับซ้อนหรือสำคัญเท่ากันหมด บางส่วนเต็มไปด้วยตรรกะธุรกิจที่ลึกซึ้ง ในขณะที่บางส่วนทำหน้าที่เพียงจัดการข้อมูลธรรมดา ฉะนั้นการเลือก design pattern ที่ “พอดี” กับแต่ละ model จึงเป็นทักษะที่คุ้มค่าแก่การฝึกฝน
ตราบใดที่คุณ แยกตรรกะของ domain ออกจากเรื่องเทคนิค (infrastructure) ได้ คุณก็ทำ Model-Driven Design — และจึงทำ DDD — ได้แล้ว ไม่ว่าจะเลือก pattern แบบใดก็ตาม นั่นคือเกณฑ์ตัดสินที่แท้จริง ไม่ใช่ข้อบังคับว่า “ต้องใช้ rich object model เท่านั้น”
Domain Layer คืออะไร
หัวข้อที่มีชื่อว่า “Domain Layer คืออะไร”domain layerDomain ModelModel ของ Domain ที่ฝังกฎและกระบวนการทางธุรกิจไว้ใน code จริง หัวใจของ DDD คือการพัฒนา domain model ที่ “เข้าใจธุรกิจอย่างลึกซึ้ง”Strategic Design คือชั้นของ code ที่อยู่ใจกลาง app และเป็นที่อยู่ของ domain model หน้าที่หลักของมันคือ กั้นความซับซ้อนของธุรกิจออกจากความซับซ้อนทางเทคนิคที่เกิดขึ้นโดยบังเอิญ (accidental technical complexity) ไม่ให้เรื่องอย่างการจัดการ transaction หรือการบันทึก state รั่วเข้ามาปะปนกับกฎธุรกิจจนกฎเหล่านั้นพร่าเลือน
สิ่งที่หลายคนแปลกใจคือ ใน app ส่วนใหญ่ domain layer กินพื้นที่ code เพียงนิดเดียว ที่เหลือเป็นเรื่อง infrastructure และการแสดงผล (presentation) ดังรูปที่ 5-1

code domain ที่ดูเหมือน “ผอม” เมื่อเทียบกับ infrastructure ทั้งหลาย — แต่มันคือส่วนที่ฝัง กฎและความได้เปรียบในการแข่งขัน ของธุรกิจไว้ จึงคุ้มค่าที่สุดที่จะทุ่มเทออกแบบ การปกป้องไม่ให้มันปนกับเรื่องเทคนิคคือเป้าหมายของ Layered Architecture ทั้งหมด
หลาย model หลายรูปแบบ ใน app เดียว
หัวข้อที่มีชื่อว่า “หลาย model หลายรูปแบบ ใน app เดียว”ระบบใหญ่ไม่ได้ถูกสร้างด้วยวิธีเดียวกันทั้งหมด ส่วนหนึ่งสำคัญน้อยกว่าอีกส่วน และอาจมีหลาย model เพื่อรองรับ context หรือทีมที่ต่างกัน รูปที่ 5-2 แสดงให้เห็นว่า app หนึ่งตัวอาจถูกแบ่งออกเป็นหลาย Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design โดยแต่ละ context เลือกใช้ pattern คนละแบบ ในการ implement model ของตน

รูปแบบสามตัวแรกที่จะพูดถึงต่อไปนี้ถูกบันทึกไว้ครั้งแรกโดย Martin Fowler ในหนังสือ Patterns of Enterprise Application Architecture:
- Domain Model — model เชิงวัตถุที่รวมทั้งข้อมูลและพฤติกรรม
- Transaction Script — จัดตรรกะเป็นขั้นตอนตามลำดับ (procedural)
- Table Module — 1 object แทนหนึ่งตารางในฐานข้อมูล
นอกจากของ Fowler เราจะเพิ่ม Active Record, Anemic Domain Model และแนวทาง functional เข้ามาด้วย แต่ละตัวมีประโยชน์ขึ้นกับความซับซ้อนของ model แต่ละก้อนใน app ของคุณ
ในระบบขนส่งสินค้า การ “วางแผนเส้นทาง” ของ Cargo เต็มไปด้วยกฎธุรกิจ (ต้นทาง ปลายทาง กำหนดเวลา ผ่านท่าเรือไหนบ้าง) — เหมาะกับ Domain Model pattern เต็มตัว
แต่ context ที่แค่ดูแล “ตารางอ้างอิงท่าเรือ” (รหัสท่า ชื่อท่า ประเทศ) เป็นแค่ฟอร์มกรอกข้อมูลธรรมดา — ใช้ Table Module หรือ Active Record ก็เพียงพอ ไม่ต้องลากทุกอย่างเข้า model เชิงวัตถุที่ซับซ้อน
Domain Model Pattern
หัวข้อที่มีชื่อว่า “Domain Model Pattern”Domain Model pattern เป็น pattern ที่ “เป็นเนื้อเดียวกับ DDD” มากที่สุด เพราะมันเหมาะกับ domain ที่ซับซ้อนและ รวยตรรกะธุรกิจ (Rich Domain ModelRich Domain Modelmodel domain ที่ “รวยพฤติกรรม” — object ที่เก็บทั้งข้อมูลและ business logic ไว้ด้วยกัน เป็นคู่ตรงข้ามของ Anemic Domain Model (ที่มีแต่ getter/setter แล้วผลัก logic ไปไว้ที่อื่น) tactical patterns ของ DDD จะคุ้มค่าก็ต่อเมื่อ model “รวย” พอเช่นนี้Tactical Design) มันคือ model เชิงวัตถุที่รวมทั้ง พฤติกรรมและข้อมูล ไว้ด้วยกัน เมื่อมองผ่านๆ อาจดูคล้าย data schema ของฐานข้อมูล แต่จริงๆ แล้วมันบรรจุกระบวนการธุรกิจ (business process) ความสัมพันธ์ กฎ และตรรกะที่ลึกกว่านั้นมาก
หัวใจของ pattern นี้คือสมมติฐานที่ว่า “ทำเหมือนไม่มีฐานข้อมูล” — model ถูกออกแบบแบบ persistence-ignorant (ไม่รู้และไม่สนว่าจะถูกเก็บลงที่ไหน) เราเริ่มจาก code model ไม่ใช่ data model — นี่คือ model-driven แทนที่จะเป็น data-driven จะค่อยมาประนีประนอมเรื่องการเก็บข้อมูลทีหลังก็ได้ object ภายใน model จึงเป็น “Plain Old Object” (POCO/POJO) ที่ปลอดจาก infrastructure

การไม่ต้องคิดเรื่อง persistence ระหว่างออกแบบ ทำให้เราสร้าง model ที่ สื่อความหมาย (expressive) และโฟกัสที่ปัญหาของ domain ล้วนๆ ได้
เวลาวาด model หลายคนเริ่มจากคำนาม (Order, Customer, Product) ซึ่งเสี่ยงจะได้แค่ภาพสะท้อนของตารางข้อมูล หนังสือแนะนำให้โฟกัสที่ กริยาและการกระทำ ของ domain ด้วย เช่น “เสนอราคาประมูล (place bid)”, “โอนเงิน”, “ใช้คูปองส่วนลด” — เพราะนั่นจะดึงความสนใจไปที่ พฤติกรรม แทนที่จะเป็นเพียง สถานะ
ใน model ที่ดี แต่ละ object รับผิดชอบงานเฉพาะของตน และ มอบหมายงาน (delegate) ให้กันเพื่อทำ use case ให้สำเร็จ ตัวอย่างจากหนังสือคือเว็บประมูล: class Auction ไม่ได้คำนวณราคาบิดถัดไปเอง แต่มอบให้ WinningBid จัดการ
public class Auction{ public void PlaceBidFor(Bid bid, DateTime currentTime) { if (StillInProgress(currentTime)) { if (FirstOffer()) PlaceABidForTheFirst(bid); else if (BidderIsIncreasingMaximumBid(bid)) // มอบหมายให้ WinningBid ตัดสินใจเรื่องการเพิ่มราคาสูงสุด WinningBid = WinningBid.RaiseMaximumBidTo(bid.MaximumBid); else if (WinningBid.CanBeExceededBy(bid.MaximumBid)) Place(WinningBid.DetermineWinningBidIncrement(bid)); } }}ในร้านออนไลน์ Order ไม่ได้มีแค่ property อย่างวันที่สร้าง สถานะ และเลขที่คำสั่งซื้อ — มันต้องบรรจุ ตรรกะการใช้คูปองส่วนลด พร้อมกฎรอบๆ ทั้งหมด: คูปองนี้ยังใช้ได้ไหม? ใช้กับสินค้าในตะกร้านี้ได้หรือเปล่า? มีโปรโมชันอื่นที่ทำให้คูปองนี้ใช้ไม่ได้หรือไม่?
เมื่อตรรกะแบบนี้ “อยู่ในตัว Order เอง” นั่นคือ Domain Model pattern ที่ทำงานได้จริง
Domain Model pattern ท้าทายทางเทคนิคที่สุด ต้องใช้นักพัฒนาที่เข้าใจ OOP เป็นอย่างดี และมีต้นทุนสูง ระบบย่อยส่วนใหญ่เป็นเพียง CRUD — มีเพียง Core DomainCore Domainsubdomain ที่สำคัญและสร้างความได้เปรียบในการแข่งขันมากที่สุด เป็นเหตุผลที่เราต้องสร้างซอฟต์แวร์เองแทนการซื้อ ควรทุ่มทีมเก่งที่สุดลงตรงนี้Strategic Design เท่านั้นที่คุ้มกับการลงทุนทำ rich domain model
ถ้าส่วนที่คุณทำเป็นเพียง “ฟอร์มกรอกข้อมูล” ที่ตรรกะแทบไม่เปลี่ยนแปลง การฝืนใช้ Domain Model pattern อย่างดีที่สุดก็เสียแรงเปล่า อย่างเลวร้ายคือเพิ่มความซับซ้อนที่ไม่จำเป็นเข้าไป
Transaction Script
หัวข้อที่มีชื่อว่า “Transaction Script”ในบรรดา pattern ทั้งหมดในบทนี้ Transaction ScriptTransaction Scriptรูปแบบที่จัดตรรกะธุรกิจเป็นชุดคำสั่งตามขั้นตอน (procedural) แทนการกระจายลงในอ็อบเจ็กต์ เหมาะกับงานง่าย ๆ แต่จะรกเมื่อ domain ซับซ้อนขึ้นArchitecture เข้าใจง่ายและเริ่มใช้ได้เร็วที่สุด มันเป็นสไตล์ procedural ไม่ใช่ OOP โดยทั่วไปจะสร้าง “1 procedure ต่อ1 business transaction” แล้วจัดกลุ่มไว้ใน manager หรือ service class แต่ละ procedure บรรจุตรรกะธุรกิจ ทั้งหมด ที่ต้องใช้ในการทำธุรกรรมนั้นให้จบ — ตั้งแต่ workflow, กฎธุรกิจ, การ validate ไปจนถึงการบันทึกลงฐานข้อมูล
ในตัวอย่างเว็บประมูล แต่ละ use case ถูกห่อเป็น class เดียวที่ implement interface ICommand ดังโครงสร้างนี้:
classDiagram
class ICommand {
+Execute()
}
class CreateAuction {
+Execute()
}
class BidOnAuction {
+Execute()
}
ICommand <|.. CreateAuction
ICommand <|.. BidOnAuction
ทั้ง use case จะถูกห่อไว้ใน method เดียว — รับผิดชอบทุกอย่างปนกัน ตั้งแต่ดึงข้อมูล บันทึกข้อมูล จัดการ transaction ไปจนถึงตรรกะธุรกิจของการวางบิด:
public class BidOnAuction : ICommand{ public void Execute() { using (var scope = new TransactionScope()) { ThrowExceptionIfNotValid(auctionId, bidderId, amount, timeOfBid); ThrowExceptionIfAuctionHasEnded(auctionId);
if (IsFirstBid(auctionId)) PlaceFirstBid(auctionId, bidderId, amount, timeOfBid); else if (IsIncreasingMaximumBid(auctionId, amount, bidderId)) IncreaseMaximumBidTo(amount); else if (CanMeetOrExceedBidIncrement(amount)) UpdatePrice(auctionId, bidderId, amount, timeOfBid); } }}จุดแข็ง: เข้าใจง่าย พาสมาชิกใหม่เข้าทีมได้เร็วโดยไม่ต้องรู้ pattern มาก่อน เพิ่มความต้องการใหม่ก็เพียงเพิ่ม method โดยแทบไม่กระทบของเดิม จึงเหมาะกับทีมที่มี junior ซึ่งยังไม่ถนัด OOP และเหมาะกับส่วนของ domain ที่มีตรรกะน้อยหรือแทบไม่มีเลย
เมื่อ app โตขึ้นและตรรกะซับซ้อนขึ้น Transaction Script จะกลายเป็นปัญหาอย่างรวดเร็ว เพราะธรรมชาติของมันทำให้เกิด code ซ้ำซ้อน (duplication) ได้ง่าย — API บวมขึ้นด้วย method ละเอียดยิบที่ทำงานทับซ้อนกัน คุณใช้ sub-method ลดการซ้ำซ้อนใน validation และกฎธุรกิจได้บ้าง แต่การซ้ำซ้อนใน workflow นั้นหลีกเลี่ยงได้ยาก
เมื่อความซ้ำซ้อนเริ่มมากเกินไป นั่นคือสัญญาณชัดเจนว่าถึงเวลา refactor ไปสู่ Domain Model pattern
Table Module และ Active Record
หัวข้อที่มีชื่อว่า “Table Module และ Active Record”Table Module จับคู่ object model เข้ากับ database model โดย 1 object แทนหนึ่งตาราง (หรือ view) object นั้นรับผิดชอบทั้งการ persist และพฤติกรรมทางธุรกิจ ข้อดีคือไม่มีความเหลื่อม (mismatch) ระหว่าง object model กับ database model มันเหมาะกับ Database-Driven Design จึงดูเหมือนจะไม่เข้ากับ DDD เมื่อมองผ่านๆ — แต่สำหรับส่วนของ domain ที่ง่ายๆ ถูกล้อมด้วย bounded context และเป็นแค่ “ฟอร์มกับข้อมูล” มันคือทางเลือกที่ดีและเรียนรู้ง่ายกว่า Domain Model pattern
Active Record เป็นญาติของ Table Module ต่างกันตรงที่ object แทน “แถว” (row) ของตาราง ไม่ใช่ตัวตารางเอง แต่ละ business object คือหนึ่งระเบียน บรรจุทั้งข้อมูล พฤติกรรม และวิธี persist ตัวเอง รวมถึง method สำหรับเพิ่ม instance ใหม่และค้นหา collection
Active Record เหมาะมากกับ app เรียบง่ายที่ data model กับ business model จับคู่ หนึ่งต่อหนึ่ง เช่น ระบบ block หรือฟอรัม และเหมาะถ้าคุณมี database model อยู่แล้ว หรือชอบสร้าง app แบบ “data first” เพราะทุก object มี CRUD เหมือนกันหมด จึง ใช้เครื่องมือ code generation สร้าง model ให้อัตโนมัติได้เลย
ข้อระวัง: ถ้าเมื่อไร object model กับ database model เริ่ม “แยกทางกัน” (diverge) นั่นคือสัญญาณให้ refactor ไปทาง Domain Model pattern
Anemic Domain Model
หัวข้อที่มีชื่อว่า “Anemic Domain Model”Anemic Domain ModelAnemic Domain Modelanti-pattern ที่อ็อบเจ็กต์มีแต่ getter/setter ไร้พฤติกรรม ตรรกะถูกดึงไปไว้ใน service ทั้งหมด — จ่ายค่า domain model แต่ไม่ได้ประโยชน์ของมันเลยTactical Design บางครั้งถูกเรียกว่า anti-pattern เมื่อมองผ่านๆ มันคล้าย Domain Model มาก — มี domain object ที่แทนแนวคิดทางธุรกิจเหมือนกัน แต่พฤติกรรมทั้งหมดไม่ได้อยู่ในตัว object มันถูกย้ายออกไปอยู่นอก model ทิ้งให้ domain object เป็นเพียง class รับส่งข้อมูล (Data Transfer Object) ที่มีแต่ getter/setter
ปัญหาหลักคือ domain service จะกลับไปเขียนแบบ procedural คล้าย Transaction Script — พร้อมปัญหาทั้งหมดของมัน หนึ่งในนั้นคือการละเมิดหลัก “Tell, Don’t Ask” ที่ว่า object ควร บอก client ว่าทำอะไรได้/ไม่ได้ แทนที่จะเปิดเผย property ให้ client ไปเดาเอาเองว่า object อยู่ในสถานะที่ทำงานนั้นได้หรือไม่
ถ้าคุณ ตั้งใจ จะทำ rich domain model แต่กลับลงเอยด้วย object ที่ไร้พฤติกรรมและ service ที่แบกรับทุกอย่าง — นั่นคือคือ anti-pattern ตัวจริง คุณจ่ายค่าความซับซ้อนของการมี model ไปเต็มๆ แต่ไม่ได้ความชัดเจนหรือการห่อหุ้มกฎกลับมาแม้แต่น้อย
แต่ Anemic Domain Model ไม่ได้แย่เสมอไป มันเป็นจุดเริ่มต้นที่ดีสำหรับส่วนที่มีตรรกะน้อย หรือสำหรับทีมที่ยังไม่ถนัด OOP และยังสามารถสะท้อน Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design ได้ จึงเป็น ก้าวแรก ที่ดีในการค่อยๆ พัฒนาต่อยอดไปสู่ rich domain model
Anemic Domain Model กับ Functional Programming
หัวข้อที่มีชื่อว่า “Anemic Domain Model กับ Functional Programming”จุดที่น่าสนใจคือ DDD เปิดกว้างเต็มที่ สำหรับคนที่ชอบ functional มากกว่า OOP เราสร้าง domain model ด้วยแนวคิด functional อย่าง immutability และ referential transparency ได้สบาย object ที่รวยพฤติกรรมไม่ใช่สิ่งจำเป็น และในบริบทนี้ Anemic Domain Model กลับกลายเป็นแนวคิดพื้นฐานที่มีประโยชน์ ไม่ใช่ anti-pattern
ฟังดูขัดกัน เพราะ model มีไว้สื่อสารกับ domain expert แต่ anemic model กลับดูเหมือนจะไม่แทนแนวคิด domain ด้วย object — อย่างไรก็ตาม นักปฏิบัติ DDD สมัยใหม่หลายคนยืนยันว่า แนวคิดที่สำคัญที่สุดของ domain คือ “กริยา” ไม่ใช่คำนามอย่าง “บัญชีธนาคาร” แต่เป็นกริยาอย่าง “การโอนเงิน” ด้วย functional programming เรายังแสดงกริยาเหล่านี้ได้เต็มที่ และคุยกับ domain expert ได้อย่างมีความหมาย
ลองดู entity BankAccount แบบ OOP ที่รวยพฤติกรรม:
public class BankAccount{ public Guid Id { get; private set; } public Money Balance { get; private set; } public Money OverdraftLimit { get; private set; }
public void Withdraw(Money amount) { /* ... */ } public void Deposit(Money amount) { /* ... */ } public void IncreaseOverdraft(Money amount) { /* ... */ }}ในแบบ functional มันจะกลายเป็น โครงสร้างข้อมูลบริสุทธิ์ที่ immutable ไร้พฤติกรรม แล้วพฤติกรรมจะอยู่ในรูป pure function แทน ความท้าทายคือการจัดกลุ่ม function เหล่านั้นให้สอดคล้องกับ model เชิงแนวคิด — ทางเลือกที่ดีคือจัดกลุ่มตาม AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design
เพราะ functional บังคับ immutability ทุก function จึงต้อง คืนค่าโครงสร้างข้อมูลชุดใหม่ แทนการแก้ state ของ object เดิม เปรียบเทียบ ShoppingBasket สองแบบ:
// โครงสร้างข้อมูล immutable บริสุทธิ์public struct ShoppingBasket{ public ShoppingBasket(Guid id, IImmutableList<BasketItem> items) { this.Id = id; this.Items = items; }
public Guid Id { get; private set; } public IImmutableList<BasketItem> Items { get; private set; }}
// รวม function ทั้งหมดของ Basket aggregate ไว้ด้วยกันpublic static class Basket{ // pure function — ไม่ผูกกับ instance ของ object ใด public static ShoppingBasket AddItem(BasketItem item, ShoppingBasket basket) { if (basket.Items.Contains(item)) throw new DuplicateItemSelected();
// การเพิ่มของจะสร้าง collection ใหม่ที่ immutable var items = basket.Items.Add(item); // และคืน basket ใหม่ที่ immutable เช่นกัน return new ShoppingBasket(basket.Id, items); }}สังเกตว่า collection Items ห่อหุ้มเป็น private ไม่ได้แล้ว เพราะ function อยู่นอก object จึงเข้าถึง state ส่วนตัวไม่ได้
ในบางภาษา การคัดลอก object อาจได้แค่ reference ที่ชี้ไปยัง object เดิม (shallow copy) เช่น ถ้าคัดลอก ShoppingBasket ทั้งต้นฉบับและสำเนาอาจชี้ไปที่ collection Items เดียวกัน — แก้สำเนา = แก้ต้นฉบับไปด้วย ทางแก้ที่ง่ายที่สุดคือ ใช้โครงสร้างข้อมูลและ collection ที่ immutable เท่านั้น (ภาษาสมัยใหม่ส่วนใหญ่รองรับ immutable collection ในตัวอยู่แล้ว)
ภาษาอย่าง Haskell, Scala, Clojure ทำให้ functional เป็น first-class แต่คุณก็สร้าง functional domain model ในภาษา OOP อย่าง C# หรือ Java ได้ ถ้ายังไม่คุ้น functional การเรียนแนวคิดพื้นฐานสักหน่อยคุ้มค่าเวลาแน่นอน
สรุปและวิธีเลือก pattern
หัวข้อที่มีชื่อว่า “สรุปและวิธีเลือก pattern”ไม่มี pattern ใด “ถูก” เสมอ — เลือกตาม ความซับซ้อนของตรรกะ และ ความสำคัญ ของแต่ละส่วน:
| Pattern | เหมาะเมื่อ | ระวัง |
|---|---|---|
| Domain Model | ตรรกะธุรกิจซับซ้อน รวยพฤติกรรม (core domain) | ต้นทุนสูง ต้องเก่ง OOP — อย่าใช้กับ CRUD ธรรมดา |
| Transaction Script | ตรรกะน้อย/แทบไม่มี ทีม junior | code ซ้ำเมื่อโตขึ้น → refactor ไป Domain Model |
| Table Module | ฟอร์มกับข้อมูล จับคู่ตารางตรงๆ | ถ้า model เริ่ม diverge → refactor |
| Active Record | หนึ่งต่อหนึ่งกับตาราง CRUD เยอะ ตรรกะต่ำ | ไม่เหมาะกับตรรกะซับซ้อน |
| Anemic + Functional | ชอบ immutability/pure function | ระวัง shallow copy; ถ้าตั้งใจทำ rich แต่ได้ anemic = anti-pattern |
domain layer กั้น model ของธุรกิจออกจาก infrastructure และ presentation — และ model นั้น implement ได้ด้วยหลาย pattern project ใหญ่มักมีหลาย model และหลาย pattern อยู่ร่วมกัน เกณฑ์เดียวที่ตัดสินคือ pattern นั้นแยกตรรกะ domain ออกจาก code เทคนิคได้หรือไม่ ถ้าได้ ก็ถือว่าเข้ากับ DDD ส่วน Domain Model pattern นั้นจงเก็บแรงไว้ทุ่มให้ core domain ที่ซับซ้อนจริงๆ เท่านั้น
EntityEntityอ็อบเจ็กต์ที่มี “ตัวตน” (identity) ต่อเนื่องตามเวลา แม้ค่าภายในเปลี่ยน ก็ยังเป็นสิ่งเดิม เช่น ลูกค้า, คำสั่งซื้อ — แยกแยะด้วย id ไม่ใช่ด้วยค่าTactical Design และ AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design คือ tactical building block ที่นักปฏิบัติ DDD ใช้แทนแนวคิด domain อย่างบัญชีธนาคารหรือตะกร้าสินค้า เราจะลงลึกเรื่องเหล่านี้ใน Part III ของหนังสือ (บทถัดๆ ไป)
เจาะลึกแนวคิดในบทนี้ต่อได้ที่คลังอ้างอิง DevIQ:
- Domain Model — นิยามอย่างเป็นทางการของ Domain Model pattern ที่บทนี้ใช้เป็นแกนหลักในการเปรียบเทียบกับ pattern อื่นๆ
- Anemic Domain Model — ขยายความว่าทำไมจึงถูกมองเป็น anti-pattern ในบริบท OOP แต่กลับมีประโยชน์ในบริบท functional ตามที่บทนี้อธิบาย
- Aggregate — หน่วยที่ใช้จัดกลุ่ม pure function เข้าด้วยกันเวลาสร้าง model แบบ functional (เช่นตัวอย่าง
ShoppingBasket) - Entity — building block ที่ domain object อย่าง
AuctionหรือBankAccountในบทนี้เป็นตัวอย่างของแนวคิดนี้
เช็กความเข้าใจ — บทที่ 5
ข้อ 1 / 4ตามหนังสือ เกณฑ์ที่แท้จริงในการตัดสินว่า ‘เข้ากับ DDD หรือไม่’ คืออะไร?