Repositories
เมื่อเราออกแบบ AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design ที่เต็มไปด้วยพฤติกรรมไว้อย่างประณีตแล้ว คำถามต่อไปคือ — เราจะ “สร้าง บันทึก และดึงกลับ” อ็อบเจ็กต์เหล่านี้อย่างไร โดยไม่ให้เรื่องของฐานข้อมูลมาปะปนกับตรรกะธุรกิจ? คำตอบของ DDD คือ RepositoryRepositoryตัวที่ทำให้เราเข้าถึง Aggregate ราวกับเป็น collection ในหน่วยความจำ ซ่อนรายละเอียดฐานข้อมูล มี1 repository ต่อ1 Aggregate RootTactical Design
RepositoryRepositoryตัวที่ทำให้เราเข้าถึง Aggregate ราวกับเป็น collection ในหน่วยความจำ ซ่อนรายละเอียดฐานข้อมูล มี1 repository ต่อ1 Aggregate RootTactical Design ทำหน้าที่จัดการการ persist และ retrieve aggregate โดยซ่อนกลไกการจัดเก็บไว้หลัง “หน้าฉากที่ทำตัวเหมือน collection” (collection façade) ผลคือ domain model ของเราไม่จำเป็นต้องรับรู้เลยว่าเบื้องหลังเป็น SQL, document store หรือ flat file — ความซับซ้อนทางเทคนิคถูกกันออกไปจาก domain อย่างหมดจด
Repository ไม่ได้มีไว้เพื่อ “ทดสอบง่าย” หรือ “สลับฐานข้อมูล” เป็นหลัก — เป้าหมายที่แท้จริงคือ กั้น domain model ออกจาก persistence framework เพื่อให้ model ของเราวิวัฒน์ได้อย่างอิสระ โดยไม่ปล่อยให้เทคโนโลยีการจัดเก็บมาเป็นตัวกำหนดรูปร่างของมัน
Repository คืออะไร และต่างจากการเข้าถึงข้อมูลแบบเดิมอย่างไร
หัวข้อที่มีชื่อว่า “Repository คืออะไร และต่างจากการเข้าถึงข้อมูลแบบเดิมอย่างไร”Repository ให้ความสามารถคล้าย collection ในหน่วยความจำ — เพิ่ม ลบ ค้นหา aggregate ได้ — แต่ต่างจากชั้น data access แบบดั้งเดิม (เช่น DAO หรือการเรียก ORM ตรงๆ) ใน 3 ข้อสำคัญ
- จำกัดการเข้าถึงไว้ที่ระดับ aggregate root เท่านั้น — เราดึงและบันทึกได้แค่ Aggregate RootAggregate RootEntity หนึ่งตัวที่เป็น “ประตูเดียว” เข้าสู่ Aggregate การอ้างอิงจากภายนอกทำได้กับ root เท่านั้น root จึงควบคุมกฎความถูกต้องของทั้งกลุ่มTactical Design ทำให้การเปลี่ยนแปลงและ InvariantInvariantกฎทางธุรกิจที่ “ต้องเป็นจริงเสมอ” ภายใน Aggregate เช่น ยอดรวมคำสั่งซื้อต้องเท่ากับผลรวมของรายการสินค้า — ต้องถูกต้องภายใน transaction เดียวTactical Design ทั้งหมดถูกควบคุมผ่าน aggregate เสมอ
- คงหน้าฉากแบบ persistence-ignorant — ซ่อนเทคโนโลยีที่ใช้ persist ไว้เบื้องหลัง client ไม่รู้และไม่จำเป็นต้องรู้ว่าข้างในเป็นอะไร
- ขีดเส้นแบ่งระหว่าง domain model กับ data model — ข้อนี้สำคัญที่สุด
interface ของ repository ถือเป็น ส่วนหนึ่งของ domain model จึงอยู่ใน namespace ของ domain ส่วน implementation จริงไปอยู่ในชั้น infrastructure
namespace DomainModel{ // interface เป็นของ domain — เขียนด้วยภาษาของธุรกิจ public interface ICustomerRepository { Customer FindBy(Guid id); void Add(Customer customer); void Remove(Customer customer); }}ส่วน implementation อยู่ในชั้น infrastructure และมักมอบงานหนักให้ persistence framework เช่น NHibernate ทำแทน โดยมี application service เป็น client หลักที่เรียกใช้งาน repository
ในระบบ Cargo Shipping เรามี CargoRepository หนึ่งตัวต่อ aggregate root Cargo หนึ่งตัว เมื่อ application service ต้องการจัดเส้นทางใหม่ให้สินค้า มันจะเรียก cargoRepository.FindBy(trackingId) เพื่อดึง Cargo กลับมาเป็น object ที่สมบูรณ์ — โดยไม่รับรู้เลยว่าเบื้องหลังเป็นการ join หลายตารางหรือการอ่าน JSON เพียงก้อนเดียว นั่นคือพลังของ collection façade
Pattern ที่ถูกเข้าใจผิดบ่อยที่สุด
หัวข้อที่มีชื่อว่า “Pattern ที่ถูกเข้าใจผิดบ่อยที่สุด”มีนักพัฒนาจำนวนมากเขียน block โจมตีว่า repository เป็น antipattern เพราะมัน “ซ่อนความสามารถของ persistence framework” แต่ Millett และ Tune ชี้ว่า — นั่นคือคือ จุดประสงค์ ของมัน
แทนที่จะเปิด interface กว้างๆ ให้ query หรือแก้ไขข้อมูลได้ทุกรูปแบบ repository ทำให้การดึงข้อมูล ชัดเจน ผ่าน named query method และจำกัดอยู่ที่ระดับ aggregate ข้อดีคือ
- ปรับจูน query ได้ง่าย เพราะรู้ชัดว่ามี query อะไรบ้าง
- สื่อ “เจตนา” ของ query ด้วยภาษาที่ domain expert เข้าใจ ไม่ใช่ SQL ดิบๆ
- เปิดเฉพาะ method persist ที่มีความหมาย ไม่ใช่ปล่อย CRUD ทุกตัวออกมาอย่างไร้ระเบียบ
ถ้าคุณ ไม่ได้ ใช้ Rich Domain ModelRich Domain Modelmodel domain ที่ “รวยพฤติกรรม” — object ที่เก็บทั้งข้อมูลและ business logic ไว้ด้วยกัน เป็นคู่ตรงข้ามของ Anemic Domain Model (ที่มีแต่ getter/setter แล้วผลัก logic ไปไว้ที่อื่น) tactical patterns ของ DDD จะคุ้มค่าก็ต่อเมื่อ model “รวย” พอเช่นนี้Tactical Design — domain เป็นแค่ CRUD ธรรมดา — repository จะกลายเป็นพิธีรีตองที่ซับซ้อนเกินเหตุ กรณีนี้การใช้ DAO ง่ายๆ หรือเรียก persistence framework ตรงๆ ย่อมดีกว่า เช่นเดียวกับ design pattern ทุกตัว การนำไปใช้กับทุกปัญหามีแต่จะ “เพิ่ม” ความซับซ้อน ไม่ใช่ลดทอน
domain model ≠ persistence model
หัวข้อที่มีชื่อว่า “domain model ≠ persistence model”ถ้าใช้ฐานข้อมูลเชิงสัมพันธ์ร่วมกับ ORM อยู่แล้ว หลายคนสงสัยว่าจะมี repository ไปทำไม ในเมื่อ ORM ก็ abstract เทคโนโลยีการจัดเก็บให้แล้ว — ประเด็นคือ ORM abstract เฉพาะ data model มันเพียงแปลงตารางและ column ให้กลายเป็น object เพื่อให้จัดการใน code ได้สะดวกขึ้นเท่านั้น
Persistence ModelPersistence Modelmodel การจัดเก็บ — โครงสร้างเก็บสถานะของ domain model ณ เวลาหนึ่ง อยู่ในรูปของตารางและ column (หรือ document) ในฐานข้อมูล ต่างจาก domain model ที่รวยด้วยพฤติกรรมและภาษาธุรกิจ ORM map ไปยัง persistence model ส่วน repository มีหน้าที่กั้น2 model นี้ไม่ให้ปนกันArchitecture ที่ ORM map ไปนั้น ต่างจาก domain model ของเราโดยสิ้นเชิง:
- domain model คือ abstraction ของ problem domain ที่เต็มไปด้วยพฤติกรรมและภาษา ประกอบด้วย EntityEntityอ็อบเจ็กต์ที่มี “ตัวตน” (identity) ต่อเนื่องตามเวลา แม้ค่าภายในเปลี่ยน ก็ยังเป็นสิ่งเดิม เช่น ลูกค้า, คำสั่งซื้อ — แยกแยะด้วย id ไม่ใช่ด้วยค่าTactical Design และ Value ObjectValue Objectอ็อบเจ็กต์ที่สำคัญที่ “ค่า” ของมัน ไม่มีตัวตนเฉพาะ สองตัวที่ค่าเท่ากันถือว่าเท่ากัน ควรเป็น immutable (เปลี่ยนค่าไม่ได้) เช่น เงิน, วันที่, พิกัดTactical Design
- persistence model คือ โครงสร้างสำหรับเก็บสถานะ ณ ช่วงเวลาหนึ่ง ประกอบด้วยตารางและ column

บางครั้ง2 model อาจดูคล้ายกันมากจนแทบจะเหมือนกัน แต่ในเชิงแนวคิดแล้วเป็นคนละเรื่อง — ORM ไม่ใช่ repository แต่ repository สามารถ ใช้ ORM เป็นเครื่องมือในการ persist ได้ บทบาทของ repository คือกั้น2 model ให้แยกจากกัน ไม่ปล่อยให้พร่าเลือนจนรวมเป็นหนึ่งเดียว
เมื่อ data model แยกจาก domain model เราจะสามารถ วิวัฒน์ domain model ได้โดยไม่ต้องพะวงเรื่องการจัดเก็บตลอดเวลา สุดท้ายแล้วมันก็ต้องถูก persist และอาจต้องประนีประนอมอยู่บ้าง — แต่ควรประนีประนอม “เมื่อจำเป็นจริงๆ เท่านั้น” และทำ หลัง จากออกแบบ model เสร็จแล้ว ไม่ใช่ตั้งแต่ต้นพร้อมกับการสร้าง model
อย่าหลงรัก Generic Repository
หัวข้อที่มีชื่อว่า “อย่าหลงรัก Generic Repository”นักพัฒนาชอบ reuse code จึงมักนิยาม IRepository<TAggregate, TId> แบบ generic ที่มีทุก method ครบ รวมถึง query method แบบเปิดให้ขยายอย่าง FindAllMatching(Expression<...>) แล้วให้ repository แต่ละตัวสืบทอดไป — แต่วิธีนี้มีปัญหาร้ายแรงอยู่สองข้อ
public class CustomerRepository : ICustomerRepository{ // aggregate บางตัวไม่รองรับ Remove แต่ interface บังคับให้มี public void Remove(Customer aggregate) { throw new NotImplementedException( "ลูกค้าถูกลบออกจาก collection ไม่ได้"); }}- มันตั้งสมมติฐานว่าทุก aggregate มีพฤติกรรมเหมือนกัน — บาง aggregate เป็น read-only บางตัวไม่รองรับการลบ สุดท้ายจึงต้องโยน
NotImplementedExceptionทิ้งไว้ ซึ่งเป็นสัญญาณว่าสัญญานี้ผิดตั้งแต่ต้น FindAllMatchingคือ leaky abstraction — การเปิดให้ส่ง query แบบ ad hoc เข้ามา ทำให้ควบคุม query ไม่ได้ ปรับ fetching strategy ไม่ได้ และนำไปสู่ปัญหา performance ที่รุนแรงในฐานข้อมูลเชิงสัมพันธ์
ทางที่ดีกว่าคือ นิยาม repository ตามความต้องการเฉพาะของมัน และตั้งชื่อ query method ให้ชัดเจน สื่อเจตนา
public interface ICustomerRepository{ Customer FindBy(Guid id); IEnumerable<Customer> FindAllThatAreDeactivated(); void Add(Customer customer);}generic repository ยังใช้ได้ดี แต่ให้ใช้ เบื้องหลัง implementation ผ่าน composition (ไม่ใช่ inheritance) เพื่อเลี่ยง code ซ้ำซ้อน คือ CustomerRepository ถือ IRepository<Customer, Guid> ไว้ภายในแล้วเรียกใช้ — วิธีนี้ทำให้ได้ทั้งสัญญาที่ชัดเจน และ การ reuse code
และถ้าคุณใช้ persistence framework อยู่แล้ว ไม่จำเป็นต้องไป abstract มันซ้ำอีกชั้น ใน concrete implementation ของ repository สามารถเรียก RavenDB หรือ Entity Framework ตรงๆ ได้เลย เพราะเราเพียงต้องการกัน “เรื่องเทคนิค” ออกจาก domain model เท่านั้น ส่วนอื่นของ app ไม่จำเป็นต้องซ่อน implementation — การซ่อนเกินเหตุมีแต่จะทำให้ผู้อ่าน code สับสน
กลยุทธ์การ persist aggregate
หัวข้อที่มีชื่อว่า “กลยุทธ์การ persist aggregate”รูปร่างของ aggregate, ขีดความสามารถของ persistence framework และข้อที่ว่าเป็นงาน greenfield หรือ brownfield — ทั้งหมดล้วนมีผลต่อวิธี persist หลักที่ต้องยึดไว้คือ ออกแบบ domain object โดยไม่คิดถึงเรื่อง persist ให้เป็น POCO/POJO ที่ปลอดจาก infrastructure code ให้มากที่สุด แล้วจึงค่อยประนีประนอม “เป็นทางเลือกสุดท้าย”
กรณีที่ framework map ได้โดยไม่ต้องประนีประนอม
หัวข้อที่มีชื่อว่า “กรณีที่ framework map ได้โดยไม่ต้องประนีประนอม”ถ้าเป็นงาน greenfield และใช้ ORM ที่รองรับ persistence-ignorant object (อย่าง NHibernate ที่ใช้ reflection ในการ persist object ที่ encapsulate อย่างเต็มที่ และมี private getter/setter ได้) เราจะ map domain model ไปยัง data model ได้โดยตรง บางตัวขอเพียง parameterless constructor — เป็นราคาเล็กน้อยที่จ่ายได้
ถ้าใช้ document store ก็ยิ่งง่ายขึ้นไปอีก — framework จะ serialize ทั้ง aggregate เป็น JSON เก็บได้เลย แทบไม่ต้องมี data model แยกต่างหากด้วยซ้ำ
กรณีที่ map ตรงๆ ไม่ได้ ต้องประนีประนอม
หัวข้อที่มีชื่อว่า “กรณีที่ map ตรงๆ ไม่ได้ ต้องประนีประนอม”ถ้า framework ไม่เอื้อให้ domain model เป็น persistence-ignorant เราต้องเลือกหนึ่งในกลยุทธ์ต่อไปนี้ ซึ่งล้วน ส่งผลกระทบ ต่อรูปร่างของ aggregate ไม่มากก็น้อย
| กลยุทธ์ | วิธีทำ | ราคาที่จ่าย |
|---|---|---|
| Public getters/setters | เปิด property ทุกตัวให้อ่าน/เขียนได้ เพื่อให้ repository map สถานะได้ง่าย | client อาจ bypass method ธุรกิจ ทำให้ aggregate ตกอยู่ในสถานะที่ละเมิด invariant |
| Memento pattern | aggregate สร้าง snapshot ของสถานะตัวเองให้นำไป persist และรู้วิธี hydrate กลับจาก snapshot นั้น | ต้องเขียน code snapshot/restore เพิ่ม แต่ยังคง encapsulate ได้อย่างเต็มที่ |
| Event stream | repository persist ทุก event ที่เกิดกับ aggregate (ดู Event SourcingEvent Sourcingเก็บทุกการเปลี่ยนสถานะเป็น event ที่เปลี่ยนไม่ได้ แล้วสร้างสถานะปัจจุบันด้วยการเล่น event ซ้ำ มัก “ต้องใช้คู่กับ CQRS” (Greg Young) มี event stream ต่อ1 AggregateArchitecture ในบทถัดไป) แล้ว replay เพื่อสร้าง aggregate กลับ | ซับซ้อนกว่า แต่ได้ประวัติการเปลี่ยนแปลงครบถ้วน |
จุดสำคัญที่หลายคนพลาด: snapshot ใน memento ไม่ใช่ data model — มันคือ “สถานะของ aggregate” ที่ยังปลอดจาก persistence framework โดย repository ยังคงต้อง map snapshot นั้นไปยัง data model อีกชั้นหนึ่ง
สมมติว่า Basket ต้องการ encapsulate property ทั้งหมด (ไม่ต้องการเปิด public setter) เราจึงให้มันสร้าง BasketSnapshot ที่บรรจุรายการสินค้าและค่าจัดส่ง จากนั้น BasketRepository จะรับ snapshot ไป map ลง data model และเมื่อดึงกลับ repository จะอ่าน data model → แปลงเป็น snapshot → เรียก Basket.CreateBasketFrom(snapshot) ผลคือ Basket ไม่เคยรู้จักฐานข้อมูลเลย แต่ก็ไม่ต้องเปิด property ให้โลกภายนอกมาทำลายกฎทางธุรกิจ
เริ่มจากบริสุทธิ์ก่อน (start pure) แล้วค่อยเป็นนักปฏิบัตินิยมเมื่อจำเป็น (be pragmatic when you need to)
— Scott Millett & Nick Tune
อย่าปล่อยให้ technical framework มาขับเคลื่อนการออกแบบ จงคิดในรูปของ aggregate และการดึงข้อมูลที่ชัดเจน ไม่ใช่ ad hoc query
Repository คือ “สัญญาที่ชัดเจน” ไม่ใช่แค่ CRUD
หัวข้อที่มีชื่อว่า “Repository คือ “สัญญาที่ชัดเจน” ไม่ใช่แค่ CRUD”สัญญาของ repository เป็นมากกว่า interface แบบ CRUD — มันคือ ส่วนต่อขยายของ domain model ที่เขียนด้วยภาษาที่ domain expert เข้าใจ และควรสร้างขึ้นจาก “ความต้องการของ use case” ไม่ใช่จากมุมมองของ data access
เทียบสองสัญญานี้
public interface ICustomerRepository{ Customer FindBy(Guid id); IEnumerable<Customer> FindAllThatMatch(Query query); // กว้างเกินไป IEnumerable<Customer> FindAllThatMatch(String hql); // รั่ว HQL ออกมา void Add(Customer customer);}public interface ICustomerRepository{ Customer FindBy(Guid id); IEnumerable<Customer> FindAllThatAreDeactivated(); IEnumerable<Customer> FindAllThatAreOverAllowedCredit(); void Add(Customer customer);}สัญญาแบบหลังใช้หลัก Tell, Don’t Ask — สั่งให้ repository ทำงานหนักโดย abstract ตัว query เอาไว้ ทำให้เห็นชัดว่าจะ optimize query แต่ละตัวที่ชั้น infrastructure ได้อย่างไร repository จึงไม่ใช่ object ทั่วไป แต่เป็น ขอบเขตเชิงกระบวนการ (procedural boundary) ที่ต้องตั้งชื่อ method อย่างพิถีพิถันไม่แพ้ object ใน domain
Transaction และ Unit of Work
หัวข้อที่มีชื่อว่า “Transaction และ Unit of Work”repository หนึ่งตัวดูแล aggregate root เพียง ชนิดเดียว แต่ business case หนึ่งอาจต้องอัปเดต aggregate หลายชนิด การจัดการ transaction จึงเป็นหน้าที่ของ Unit of Work ซึ่งคอยติดตามการเปลี่ยนแปลงทั้งหมดในระหว่างทำ task หนึ่ง แล้วประสานการบันทึกลง store ภายใน transaction เดียว — หากมีอะไรพลาดกลางทาง ก็จะ rollback ทั้งหมดเพื่อรักษาความถูกต้องของข้อมูล
ใน NHibernate นั้น unit of work ถูก implement ด้วย ISession รูปด้านล่างแสดงลำดับการทำงานเมื่อ application service หนึ่งใช้ repository สองตัว (OrderRepository และ LoyaltyAccountRepository) ร่วมกันในการสะสมแต้ม

กุญแจสำคัญคือ repository ทุกตัว ต้องใช้ session instance เดียวกัน กับที่ application service ใช้ จึงจะอยู่ใน transaction เดียวกัน ซึ่งทำได้ด้วยการ inject session ผ่าน constructor หรือ setter
unit of work เป็น “เรื่องเทคนิคล้วนๆ” ไม่เกี่ยวกับ domain การยัด method อย่าง EnlistIn(session) ลงใน interface ของ repository โดยตรงจึงไม่สมเหตุสมผล ให้ใช้หลัก interface segregation แยกออกเป็น IEnlistInAUnitOfWork ต่างหาก เพื่อไม่ให้สัญญาของ domain แปดเปื้อนด้วยเรื่อง session
Save หรือไม่ Save?
หัวข้อที่มีชื่อว่า “Save หรือไม่ Save?”ในอุดมคติ repository ควรทำตัวเหมือน collection ใน .NET — collection ถือ reference ไปยัง object ดังนั้นการแก้ object จึงไม่จำเป็นต้อง “อัปเดต” กลับเข้า collection นั่นหมายความว่า ไม่ควรมี method Save/Update แต่ในทางปฏิบัติสิ่งนี้ขึ้นอยู่กับ framework
- framework ที่ track การเปลี่ยนแปลงได้ (เช่น NHibernate) — ไม่จำเป็นต้องมี
Saveเพราะการแก้ aggregate จะถูก push ลง store ให้เองเมื่อ unit of work ถูก commit - framework ที่ track ไม่ได้ (micro ORM, raw ADO.NET) — จำเป็นต้องมี
Saveบนสัญญา และ application service ต้องเรียกมันด้วยตัวเองหลังจากแก้ aggregate
อย่าเผลอใส่ flag ที่บอกว่า object “ถูกแก้แล้ว” ลงใน domain object เพราะนั่นคือการนำเรื่อง persistence เข้าไปทำให้ domain model ขุ่นมัว จงปล่อยให้การ track การเปลี่ยนแปลงเป็นหน้าที่ของ unit of work และ repository ทั้งหมด
Repository ในฐานะ Anticorruption Layer
หัวข้อที่มีชื่อว่า “Repository ในฐานะ Anticorruption Layer”ในงาน brownfield ที่ต้องเชื่อมกับ persistence store เดิม repository ช่วยรักษา domain model ให้บริสุทธิ์ได้ โดยทำตัวเป็น Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design — สร้าง model ใหม่ที่รูปร่างไม่ถูกกำหนดโดยความซับซ้อนของ infrastructure เดิม
repository map ไปยัง aggregate ไม่ใช่ตาราง — entity หลายตัวอาจอยู่ในตารางเดียวกัน หรือ aggregate หนึ่งอาจกระจายอยู่หลายตาราง หลายฐานข้อมูล หรือแม้แต่ flat file, web service และ NoSQL ปะปนกัน ไม่ว่า store จะมีหน้าตาอย่างไร มันต้องไม่เป็นตัวกำหนดรูปร่างของ domain model

ความรับผิดชอบอื่นๆ ของ Repository
หัวข้อที่มีชื่อว่า “ความรับผิดชอบอื่นๆ ของ Repository”นอกจาก persist และ retrieve แล้ว repository ยังเปิดเผยข้อมูลสรุปอื่นๆ เกี่ยวกับ collection ของมันได้ เช่น
- นับจำนวน aggregate — เพิ่ม method
int Count()ลงในสัญญา - สร้าง entity ID — ถ้า ID ถูก seed โดยฐานข้อมูลหรือ infrastructure service เราสามารถ abstract มันไว้หลัง repository เช่น
int GenerateId()ให้ application service เรียกใช้อย่างชัดเจน หรือจะให้ store สร้าง ID ให้ตอน persist ก็ได้
public interface ICustomerRepository{ Customer FindBy(Guid id); IEnumerable<Customer> FindAllThatAreDeactivated(); void Add(Customer customer); int Count(); int GenerateId();}Repository คือ collection façade ที่กั้น domain model ออกจาก persistence model โดยเข้าถึงได้ผ่าน aggregate root เท่านั้น สัญญาของมันเขียนด้วยภาษาธุรกิจและสื่อเจตนาอย่างชัดเจน (ไม่ใช่ CRUD ดิบๆ) หลักคือ ออกแบบ domain ให้บริสุทธิ์ก่อน แล้วจึงเลือกกลยุทธ์ persist (map ผ่าน ORM โดยตรง, memento หรือ event stream) ตามขีดความสามารถของ framework — และประนีประนอมเมื่อจำเป็นจริงๆ เท่านั้น
เจาะลึกแนวคิดในบทนี้ต่อได้ที่คลังอ้างอิง DevIQ:
- Repository — คำอธิบายแก่นของ pattern นี้โดยตรง ตรงกับสิ่งที่บทนี้พูดถึงตลอด: collection façade ที่กั้น domain model ออกจาก persistence
- Unit of Work — ขยายความเรื่องการประสาน transaction ข้าม repository หลายตัว ที่บทนี้อธิบายไว้ในหัวข้อ “Transaction และ Unit of Work”
- Anti-Corruption Layer — บทนี้กล่าวถึง repository ในฐานะ anticorruption layer สำหรับงาน brownfield โดยตรง หน้านี้ขยายความว่า ACL คืออะไรในบริบทกว้างขึ้น
เช็กความเข้าใจ — บทที่ 21
ข้อ 1 / 4ตามหนังสือ เป้าหมาย ‘ที่แท้จริง’ ของ repository pattern คืออะไร?