ข้ามไปยังเนื้อหา
แก่น Kaen
ค้นหา
Ctrl
K
ยกเลิก
เลือกธีม
มืด
สว่าง
อัตโนมัติ
C# สมัยใหม่สำหรับ DDD — ปูพื้นก่อนลงมือ
จุดเริ่มต้นที่แนะนำของสาย .NET/DDD — 8 บทเรียน สร้าง domain Order ตั้งแต่ศูนย์ด้วย C# สมัยใหม่ ตั้งแต่ record จนถึง async/await
เริ่มบทที่ 1
ความคืบหน้า
0 / 8 บทเรียน
เส้นทางการเรียน
หัวข้อที่มีชื่อว่า “เส้นทางการเรียน”
●
01
record ที่กลายเป็น Value Object
เริ่มตรงนี้
Record
type พิเศษของ C# (ตั้งแต่ C# 9) ที่ออกแบบมาสำหรับเก็บ 'ค่า' ไม่ใช่ 'ตัวตน' — เทียบความเท่ากันด้วยค่าในทุก field ให้อัตโนมัติ ไม่ต้องเขียน Equals/GetHashCode เอง เหมาะเป็นฐานของ Value Object เช่น `record Money(decimal Amount, string Currency)`
Value Object
อ็อบเจ็กต์ที่นิยามด้วย 'ค่า' ไม่มี identity ของตัวเอง สองตัวที่ค่าเท่ากันถือว่าเป็นสิ่งเดียวกัน ควร immutable เปลี่ยนแปลงไม่ได้หลังสร้าง — ใน C# มักสร้างด้วย record เช่น Money, OrderId
○
02
init / required / with — สร้างครั้งเดียวแล้วแช่แข็ง
Init-only Setter
setter แบบ `init` ที่กำหนดค่าได้ครั้งเดียวตอนสร้างอ็อบเจ็กต์ (หรือใน object initializer) แล้วหลังจากนั้นเปลี่ยนไม่ได้อีก — เครื่องมือหลักที่ทำให้ property เป็น immutable โดยไม่ต้องพึ่ง constructor ยาว ๆ
Required Member
keyword `required` หน้า property ที่บังคับให้ผู้เรียกต้องกำหนดค่าตอนสร้างอ็อบเจ็กต์ ไม่งั้น compiler ฟ้อง — ช่วยรักษา invariant 'ต้องมีค่านี้เสมอ' ตั้งแต่จุดสร้าง โดยไม่ต้องเขียน constructor เอง
With Expression
ไวยากรณ์ `x with { Property = newValue }` ที่สร้าง record ตัวใหม่โดยคัดลอกทุก field จาก x แล้วเปลี่ยนเฉพาะที่ระบุ — วิธีมาตรฐานในการ 'แก้ไข' Value Object แบบ immutable โดยไม่ต้อง mutate ของเดิม
Immutability
คุณสมบัติ 'เปลี่ยนแปลงไม่ได้หลังสร้าง' ของอ็อบเจ็กต์ — ถ้าจะ 'เปลี่ยนค่า' ต้องสร้างอ็อบเจ็กต์ใหม่แทนของเดิม หัวใจของ Value Object ที่ทำให้ share ค่าข้ามที่ต่าง ๆ ได้อย่างปลอดภัยโดยไม่ต้องกลัวใครมาแก้ข้างหลัง
○
03
Nullable reference types — ไล่ null ออกจาก domain
Nullable Reference Types
feature ที่ทำให้ compiler ช่วยเตือนเมื่อ reference type (เช่น string, class) อาจเป็น null โดยไม่ได้ตรวจก่อน เปิดด้วย `<Nullable>enable</Nullable>` — ทำให้ null เป็นเรื่องที่ต้องประกาศชัดเจน (`string?`) ไม่ใช่ default ที่แอบซ่อน bug และผลักให้เขียน Guard Clause ตรง ๆ แทนการเดา
Guard Clause
เงื่อนไขตรวจสอบข้อมูลไว้ต้น ๆ ของ constructor หรือ method แล้วโยน exception ทันทีถ้าไม่ผ่าน เช่น `if (amount < 0) throw new ArgumentException(...)` — วิธีที่ Value Object และ Entity ใช้รักษา invariant ของตัวเองตั้งแต่วินาทีที่สร้าง ไม่ปล่อยให้อ็อบเจ็กต์ผิดรูปหลุดออกไป
○
04
Pattern matching — สถานะและการตัดสินใจโดยไม่ต้อง if ซ้อน
Pattern Matching
ไวยากรณ์ที่เช็ค 'รูปร่าง' ของค่าและดึงส่วนย่อยออกมาใช้ในขั้นตอนเดียว เช่น `is OrderStatus.Placed` หรือจับคู่ type/ค่าใน switch — เครื่องมือหลักในการเขียนกฎเปลี่ยนสถานะของ Order ให้ครบทุกกรณีอย่างปลอดภัย
Switch Expression
รูปแบบ `switch` ที่ 'คืนค่า' ได้โดยตรง ต่างจาก switch statement แบบเดิมที่สั่งงานเป็นคำสั่ง กระชับกว่าและ compiler ช่วยเตือนถ้าลืมบางเคส — ใช้เขียนการเปลี่ยนสถานะของ Order ให้ครบทุก branch เช่น `state switch { Placed => ..., Confirmed => ... }`
○
05
Enum, struct และ type system — ทำให้สถานะที่ผิดเป็นไปไม่ได้
Enum
type ที่จำกัดค่าไว้เป็นชุดคงที่ที่ตั้งชื่อไว้ล่วงหน้า เช่น `enum OrderStatus { Placed, Confirmed, Preparing, PickedUp, Delivered }` — วิธีพื้นฐานที่สุดในการแทนสถานะจำกัดจำนวนของ state machine ใน domain
Readonly Struct
struct ที่ทุก field เป็น readonly ทำให้ instance เปลี่ยนแปลงไม่ได้หลังสร้าง — เป็น value type ที่ readonly ทั้งก้อน คัดลอกด้วยค่า ไม่มี identity ตัวเลือกที่เบากว่า record สำหรับ Value Object ขนาดเล็กที่ต้องการ performance เช่น strongly-typed ID
Strongly-typed ID
การห่อ id ดิบ (เช่น Guid หรือ int) ด้วย type เฉพาะของมันเอง เช่น `OrderId`, `CustomerId` แทนการส่ง Guid เปล่า ๆ ไปมา — compiler จะจับได้ทันทีถ้าเผลอส่ง CustomerId ไปที่ parameter ที่ต้องการ OrderId
○
06
Collections และ LINQ — รักษา invariant ของกลุ่ม
LINQ
ชุด extension method (`Where`, `Select`, `Sum`, `Any` ฯลฯ) ที่ทำให้ query collection ในหน่วยความจำได้ด้วยไวยากรณ์เดียว ไม่ต้องเขียน loop เอง เช่น หาผลรวมราคาทุกบรรทัดใน Order ด้วย `Lines.Sum(l => l.Total)`
Backing Field
field ส่วนตัว (private) ที่เก็บข้อมูลจริงอยู่ข้างหลัง property หรือ collection ที่เปิดให้ภายนอกเห็นแบบอ่านอย่างเดียว เช่น `private readonly List<OrderLine> _lines` กับ property `IReadOnlyList<OrderLine> Lines` — วิธีที่ Aggregate รักษาไม่ให้ code ภายนอกมา Add/Remove ตรง ๆ โดยข้ามกฎธุรกิจ
○
07
Interface, generics และ DI — port และการพึ่งพา abstraction
Interface
สัญญา (contract) ที่ประกาศว่า 'ต้องมี method/พฤติกรรมอะไรบ้าง' โดยไม่ผูกกับวิธี implement เช่น `IOrderRepository` — ทำให้ domain พึ่งพาแค่ abstraction ส่วน implementation จริง (เช่นต่อฐานข้อมูล) ใส่เข้ามาทีหลังผ่าน Dependency Injection
Generics
ความสามารถเขียน type หรือ method ที่ทำงานกับ type ใดก็ได้โดยไม่ต้องรู้ล่วงหน้า ระบุด้วย type parameter เช่น `List<T>`, `IReadOnlyList<OrderLine>` — ทำให้เขียน code (เช่น Repository หรือ collection ห่อหุ้ม) ที่ type-safe และใช้ซ้ำได้กับหลายชนิดข้อมูล
Primary Constructor
ไวยากรณ์ย่อ (C# 12) ที่ประกาศ parameter ของ constructor ไว้บนหัว class/struct ได้เลย เช่น `class DeliveryFeeCalculator(IDistanceService distance)` — ลด code boilerplate ของการเขียน field + constructor แยกกันเวลาที่ต้องการแค่เก็บ dependency หรือค่าที่รับเข้ามา
Dependency Injection
เทคนิคที่อ็อบเจ็กต์ 'รับ' dependency ของมันเข้ามาจากภายนอก (ผ่าน constructor เป็นหลัก) แทนที่จะสร้างเองข้างใน — ทำให้สลับ implementation ได้ (เช่น mock ตอน test) และ code พึ่งพาแค่ interface ไม่ผูกกับรายละเอียด infrastructure
○
08
async/await และ Task — I/O อยู่ที่ขอบ
Async and Await
keyword คู่ที่ทำให้เขียน code รอผลลัพธ์ที่ใช้เวลา (เช่น เรียกฐานข้อมูลหรือ API ภายนอก) แบบไม่ block thread — method ที่มี `async` คืนค่าเป็น `Task`/`Task<T>` และ `await` คือจุด 'หยุดรอ' ผลโดยไม่ค้าง thread ไว้เฉย ๆ จำเป็นเมื่อ Repository หรือ port อื่นต้องคุยกับโลกภายนอก
Cancellation Token
อ็อบเจ็กต์ (`CancellationToken`) ที่ส่งต่อเข้าไปใน method async เพื่อให้ผู้เรียก 'ยกเลิก' งานที่กำลังทำอยู่ได้ เช่น ผู้ใช้ปิดหน้าเว็บระหว่างรอผล — method ที่ดีควรรับ parameter นี้และส่งต่อไปให้ทุก call ข้างในด้วย (เช่น query ฐานข้อมูล)