Domain Primitives
คำว่า "Domain Primitive": เกิดจากการนำแนวคิด Primitive Type ของภาษาโปรแกรมมิ่ง (เช่น int, String) มาแทนที่ด้วย "อิฐบล็อกระดับอะตอมในมุมมองของโดเมนธุรกิจ"
คำตอบและหมุดเชื่อมโยง: หากเราเลิกใช้ชนิดข้อมูลกว้างๆ ของภาษาที่รับค่าอะไรก็ได้ แล้วเปลี่ยนมาใช้ "โดเมนพริมิทิฟ" เป็นหน่วยที่เล็กที่สุดในการสร้างระบบ ตัว Domain Primitive นั่นเองที่จะการันตีเงื่อนไขเหล็กว่า "ถ้าวัตถุนี้มีตัวตนอยู่ได้ในระบบ แปลว่ามันต้องถูกต้อง 100% เสมอ" (If it exists, it is valid) โดยบังคับตรวจ Invariants ใน Constructor ตั้งแต่วินาทีที่สร้าง ซึ่งช่วยตัดปัญหา Injection, ข้อมูลรั่วไหล (Data Leakage) และการละเมิดกฎธุรกิจออกไปโดยธรรมชาติ!
Day 1: Security Concern / Day 2: The Anti-Hamlet / Day 3: DDD Core Tools / Day 4: Immutability & Validation Pipeline 5 ขั้น
Day 5 จาก 14 วัน — รวมร่าง Value Objects + Validation สู่ "Domain Primitives" อิฐบล็อกที่แข็งแกร่งที่สุดในสถาปัตยกรรม
เข้าใจกฎเหล็ก 4 ข้อ, Hardening APIs ด้วย Library, ป้องกันข้อมูลลับรั่วไหลด้วย Read-Once Objects, และ Decluttering Entities
เรื่องราวและมโนทัศน์เชิงลึก (Core Concepts & Narrative Scenarios)
📖 จาก -1 สู่ Quantity Domain Primitive
ใน Chapter 2 คดี Anti-Hamlet เกิดจากการใช้ int รับจำนวนสินค้า ทำให้ผู้ใช้ป้อน -1 เล็ดลอดเข้ามาทำลายดุลบัญชี เมื่อคุยกับ Domain Expert เราพบว่า: "จำนวนสั่งซื้อต้องเป็น 1 ถึง 200 เท่านั้น (0 ไม่ถือว่าสั่งซื้อ เกิน 200 ต้องติดต่อฝ่ายขายส่ง)"
Quantity ที่มี private final int value, ตรวจสอบ inclusiveBetween(1, 200, value) ใน Constructor ทันที และห่อหุ้มพฤติกรรมโดเมน เช่น add(Quantity addend) ทำให้ค่าติดลบไม่มีทางเกิดขึ้นในระบบได้อีกเลย!
🛡️ Hardening APIs ด้วย Domain Primitive Library
void sendAuditLogs(InetAddress serverAddress);ผู้เรียกอาจเผลอหรือตั้งใจส่ง IP สาธารณะภายนอกเข้ามาแอบดูด Audit Log ข้อมูลความลับออกไป
void sendAuditLogs(InternalAddress serverAddress);บังคับใช้
InternalAddress ที่การันตี IP ภายในเครือข่ายความปลอดภัยเท่านั้น เมธอดเหลือเช็กแค่ notNull() คอมไพเลอร์จะบล็อก IP นอกเครือข่ายตั้งแต่ Compile-time!
💥 เหตุการณ์รั่วไหลบนเซิร์ฟเวอร์ Tomcat
ทีมนักพัฒนานำอ็อบเจกต์ User ไปเก็บไว้ใน Web Session ของ Tomcat เพื่อแคชข้อมูล ต่อมาฝ่ายธุรกิจขอเพิ่มข้อมูลเลขบัตรประชาชน (SSN) และรหัสผ่านลงในคลาส User
🛡️ 5 กลไกคุ้มกันของ Read-Once Objects
value() ได้เพียงครั้งเดียว หากเรียกซ้ำจะโยน Exception ทันที
char[] แทน String เพื่อสั่ง Arrays.fill(value, '0') ล้างข้อมูลออกจากแรมทันทีหลังอ่าน
Externalizable แล้วโยน UnsupportedOperationException บล็อก Tomcat จากการแอบเซฟลงดิสก์
"*****" ป้องกันรั่วไหลใน Log และทำคลาสเป็น final ป้องกันการแอบสืบทอด
🧽 Entity ไม่ใช่แม่เหล็กดูดโค้ด (Decluttering Entities)
เมื่อ Entity แบกรับการเช็ก String, Regex, และ Checksum เมธอดจะมีโค้ดยาวเป็นร้อยบรรทัดจนนักพัฒนาเกิด Confirmation Bias มองข้ามจุดบกพร่องไป
🧪 ความเชื่อมโยงกับ Taint Analysis ในเชิงทฤษฎี
ในวิทยาการคอมพิวเตอร์ Taint Analysis ใช้ติดตามข้อมูลสกปรกจากภายนอก:
บังคับตรวจ Invariants ทั้งหมดใน Constructor (Fail-Fast) ถ้ามีตัวตนอยู่ในระบบได้ แปลว่าต้องถูกต้อง 100% เสมอ
กำหนดทุก Field เป็น private final และคลาสเป็น final ห้ามเปิด Setter ให้แก้ไขค่าหลังสร้างเด็ดขาด
ห้ามใช้ int, String, หรือ null ลอยๆ แทนแนวคิดโดเมนที่มีกฎทางธุรกิจกำกับ
รวบรวมพฤติกรรมและการคำนวณของโดเมนไว้ในคลาสเอง (เช่น เมธอด add() ของ Quantity)
แบบฝึกหัดปฏิบัติตามสถานการณ์จริง (Hands-on Refactoring)
// โค้ดเดิม: Order แบกภาระสแกนตัวอักษรและ Checksum ไว้ในตัวเอง public class Order { private List<OrderLine> items = new ArrayList<>(); public void addItem(String isbn, int quantity) { // แบก Size, Lexical และ Syntax Checks ไว้ใน Entity if (isbn == null || isbn.length() != 10) { throw new IllegalArgumentException("Invalid ISBN length"); } if (!isbn.matches("^[0-9]{9}[0-9X]$")) { throw new IllegalArgumentException("Invalid ISBN format"); } // สุ่มเสี่ยงที่จะลืมเช็ก quantity <= 0 หรือลืมเช็ก Checksum! if (quantity <= 0) { throw new IllegalArgumentException("Quantity must be positive"); } Book book = bookCatalog.findByIsbn(isbn); items.add(new OrderLine(book, quantity)); } }
String isbn ใน Service อื่น โปรแกรมเมอร์ต้องเขียนโค้ดสแกน Regex ซ้ำอีกรอบ
// 1. Domain Primitive: ISBN ตรวจสอบสมบูรณ์แบบตั้งแต่ตอน new public final class ISBN { private final String isbn; public ISBN(final String rawIsbn) { notNull(rawIsbn, "ISBN cannot be null"); isTrue(rawIsbn.length() == 10, "ISBN length must be 10"); isTrue(rawIsbn.matches("^[0-9]{9}[0-9X]$"), "Invalid syntax"); isTrue(checksumValid(rawIsbn), "Checksum failed"); this.isbn = rawIsbn; } public String value() { return isbn; } } // 2. Decluttered Order Entity: เหลือแค่ตรวจ Semantic Validation public class Order { private final List<OrderLine> items = new ArrayList<>(); private boolean paid = false; public void addItem(final ISBN isbn, final Quantity quantity) { notNull(isbn, "ISBN required"); notNull(quantity, "Quantity required"); // Entity เหลือเพียงตรวจกฎความหมายทางธุรกิจเท่านั้น! validState(!paid, "Cannot add items to a paid order"); Book book = bookCatalog.findByIsbn(isbn); items.add(new OrderLine(book, quantity)); } }
Order.addItem สะอาดและปลอดภัย 100% ตัวแปร isbn และ quantity ที่ส่งเข้ามาการันตีว่าถูกต้องตามกฎธุรกิจมาแล้วตั้งแต่เกิด!
ชุดควิซประเมินความรู้ประจำวัน
ตอบปุ๊บ รู้ผลเฉลยทันที พร้อมคำอธิบายและหลอดพลังงาน Energy Charge
ทดสอบความรู้ Day 5 สำเร็จแล้ว!
สรุปการเชื่อมโยงกลับสู่ Chapter 5 — Domain primitives
เรื่องราวการแก้อุปาทาน Anti-Hamlet ด้วย Quantity, การ Hardening APIs ด้วย InternalAddress, การใช้ Read-Once Objects บล็อกไม่ให้ Tomcat แอบเขียนรหัสผ่านลงดิสก์, และการสะสาง Entity ในวันนี้ ล้วนสะท้อนคำตอบของหัวข้อบท "Domain primitives" อย่างสมบูรณ์แบบว่า เมื่อเราสร้างระบบขึ้นมาจากอิฐบล็อกระดับรากฐานที่เป็น Domain Primitives ตัวประเภทข้อมูลเหล่านี้นี่เองที่จะทำหน้าที่เป็นเกราะคุ้มกันความปลอดภัย ย่อยสลายความซับซ้อน และกำจัดช่องโหว่ทางซอฟต์แวร์ให้หมดไปอย่างยั่งยืนครับ!