🛡️
14-Day Mastery Roadmap

SECURE BY DESIGN DAY 06

เริ่มทำควิซ ⚡
Part 2: Fundamentals | Chapter 6

Ensuring Integrity of State

Consistent on Creation 🏛️
📌 TITLE ANCHOR QUESTION ทำไมบทนี้ถึงชื่อนี้? และคำว่า State Integrity หมายถึงอะไร?

คำตอบและหมุดเชื่อมโยง: คำว่า "State Integrity" หมายถึง ความถูกต้อง สมบูรณ์ และสอดคล้องกับกฎทางธุรกิจของสถานะภายในของวัตถุ (Internal State) ตั้งแต่วินาทีแรกที่วัตถุถือกำเนิดขึ้น ไปจนถึงทุกๆ การเปลี่ยนสถานะตลอดช่วงชีวิตของมัน

ผู้เขียนตั้งชื่อบทนี้ว่า Ensuring integrity of state เพื่อเตือนสติว่า ช่องโหว่ความปลอดภัยส่วนใหญ่ไม่ได้เกิดจากการถูกเจาะผ่านเครือข่าย แต่เกิดจากการที่เราปล่อยให้วัตถุในระบบ "ถูกสร้างขึ้นแบบพิการ (Partially Initialized)" หรือเปิด Unrestricted Setters ทิ้งไว้ให้โค้ดภายนอกเข้ามาแอบแก้ไขสถานะได้ตามใจชอบ การปกป้อง State Integrity จึงเป็นปราการสำคัญในการป้องกันไม่ให้ระบบตกอยู่ในสถานะที่ผิดปกติหรือถูกชักจูงโดยแฮกเกอร์!

วันก่อนหน้า (Recap Days 1–5)

Day 1: CIA-T / Day 2: The Anti-Hamlet / Day 3: DDD Core / Day 4: Immutability & Validation Pipeline / Day 5: Domain Primitives

ตำแหน่งปัจจุบัน (Where We Are)

Day 6 จาก 14 วัน — สวมความปลอดภัยให้แก่ Entities & Aggregate Roots ที่มีการเปลี่ยนแปลงสถานะ

เป้าหมายประจำวัน (Core Mission)

สร้างความแข็งแกร่งด้วย Consistent on Creation, Intent-Revealing Operations, Guarded Collections และ Self-Destructing Builders

02

เรื่องราวและมโนทัศน์เชิงลึก (Narrative Scenarios & Architectural Shields)

🛒 SCENARIO 2.1 • โศกนาฏกรรมสั่งซื้อสินค้าไร้ที่อยู่จัดส่ง และหลักการ CONSISTENT ON CREATION Partially Initialized Trap

📖 เรื่องราวจริงในระบบ e-Commerce

ระบบสั่งซื้อสินค้าเกิดปัญหารายการสั่งซื้อค้างนับพัน และระบบคลังสินค้าล่มอย่างไม่ทราบสาเหตุ เมื่อสืบสวนพบว่า โปรแกรมเมอร์ออกแบบคลาส Order ด้วย No-Arg Constructor และเรียก Setter ทีละบรรทัด:

Order order = new Order();
order.setCustomerId(customerId);
// เกิด Network Timeout หรือ Exception กลางคันก่อน setShippingAddress()!
orderService.save(order); // วัตถุพิการถูกบันทึกลง Database!
💥 หายนะที่เกิดขึ้น: วัตถุ Order ถูกบันทึกลงฐานข้อมูลทั้งที่ ShippingAddress = null เมื่อคลังสินค้าดึงไปรัน เกิด NullPointerException เซิร์ฟเวอร์ค้าง และคำสั่งซื้อติดค้างเป็นลูกโซ่!

🧠 ถอดรหัสสู่โลกซอฟต์แวร์ (Mental Model)

🚗 Analogy: เปรียบเหมือน "โรงงานประกอบรถยนต์ที่ปล่อยให้รถวิ่งออกจากสายการผลิตไปอยู่บนถนนจริง ทั้งที่ยังไม่ได้ติดตั้งพวงมาลัยและเบรก!" รถคันนี้กลายเป็นวัตถุพิการอันตรายที่พร้อมจะชนคนตายได้ทุกเมื่อ
🛡️ Consistent on Creation: วัตถุ Entity ต้องสมบูรณ์ สมเหตุสมผล และสอดคล้องกับกฎธุรกิจ 100% ตั้งแต่วินาทีแรกที่ถูกสร้างขึ้น (Valid on Creation) ห้ามปล่อยให้วัตถุพิการหลุดออกมามีตัวตนอยู่ในระบบเด็ดขาด!
🔑 SCENARIO 2.2 • ประตูที่เสียบกุญแจค้างไว้ และอันตรายของ UNRESTRICTED SETTERS Broken Encapsulation

📖 เรื่องราวจริงในคลาส BankAccount

ในคลาส BankAccount โปรแกรมเมอร์สร้าง Private Field private double balance; แต่เปิดเมธอด public void setBalance(double balance) เอาไว้ตามความเคยชินของ JavaBeans

💥 ช่องโหว่ความปลอดภัย: โค้ดส่วนอื่นในระบบ (หรือผู้โจมตีที่ใช้ Reflection / Injection) สามารถสั่ง account.setBalance(1000000.0) ได้โดยตรง! การเสกเงินหนึ่งล้านบาทนี้ไม่ต้องผ่านขั้นตอน Deposit, ไม่ผ่านการตรวจสอบสิทธิ์, และไม่มีการบันทึก Audit Log เลยแม้แต่บรรทัดเดียว!

🧠 ถอดรหัสสู่โลกซอฟต์แวร์ (Mental Model)

🚪 Analogy: เหมือน "ประตูบ้านที่ติดตั้งแม่กุญแจนิรภัยราคาสูง แต่กลับเสียบลูกกุญแจค้างไว้ที่หน้าประตู" ใครเดินผ่านไปมาก็หมุนลูกบิดเปิดเข้ามาแก้ไขสิ่งของภายในบ้านได้ทันที
🛡️ Intent-Revealing Domain Operations: เลิกใช้ Unrestricted Setters แล้วเปลี่ยนเป็นเมธอดที่สื่อเจตนาทางธุรกิจ เช่น deposit(Money amount) หรือ withdraw(Money amount) ซึ่งบังคับตรวจ Invariants และ Audit Trail ภายในตัวเองเสมอ
📖 SCENARIO 2.3 • การขโมยสมุดบัญชีธนาคาร และภัยคุกคาม ESCAPING REFERENCES Collection Leakage

📖 รอยรั่วของ Getter ที่ส่งออก Collection ฉบับจริง

ในคลาส Order (Aggregate Root) มีการเก็บรายการสินค้า private List<OrderLine> items; และสร้าง Getter คืนค่าอ้างอิงตรงๆ:

public List<OrderLine> getItems() {
    return this.items; // Danger: Escaping Reference!
}
💥 ช่องโหว่ความปลอดภัย: โค้ดภายนอกสั่ง order.getItems().clear() ลบรายการสินค้าทิ้ง หรือสั่ง order.getItems().add(fakeItem) แอบยัดสินค้าขยะเข้ามา โดยที่ Aggregate Root อย่าง Order ไม่รู้เห็นเลยแม้แต่น้อย!

🧠 ถอดรหัสสู่โลกซอฟต์แวร์ (Mental Model)

🏦 Analogy: เหมือน "เจ้าหน้าที่ธนาคารยื่นสมุดบัญชีฉบับจริงให้คนนอกเอาไปเขียนเพิ่มหรือใช้น้ำยาลบคำผิดลบเองตามใจชอบ" แทนที่จะต้องทำรายการผ่านเคาน์เตอร์ธนาคาร
🛡️ Guarding Collections: ส่งออกเฉพาะ Collections.unmodifiableList(items) เพื่อบล็อกคำสั่ง add/remove/clear จากภายนอก และทำ Defensive Copying ใน Constructor เพื่อตัดการเชื่อมโยงกับ List ภายนอก!
🏛️ 4 เสาหลักในการพิทักษ์ STATE INTEGRITY Defensive Architecture
1. Mandatory Constructors

ส่งค่า Mandatory Fields ทั้งหมดผ่าน Constructor ล็อคฟิลด์ที่ไม่เปลี่ยนเป็น private final เพื่อขจัด No-Arg Constructor และตัดวงจรอ็อบเจกต์พิการ

Rule: Consistent on Creation
2. Domain Operations

เปลี่ยนจาก setPaid(true) หรือ setStatus("CANCELLED") เป็นเมธอดสื่อเจตนา เช่น markPaid(), cancel() ที่มี Precondition เช็กสถานะก่อนรัน

Rule: Intent-Revealing
3. Advanced Invariants & checkInvariants()

รวบรวมเงื่อนไขข้ามหลายฟิลด์ (เช่น บัญชีต้องมี Credit Limit หรือ Fallback Account อย่างใดอย่างหนึ่งเท่านั้น — XOR) ไว้ใน checkInvariants() และตรวจตอนจบทุกเมธอด

Rule: Cross-Field Invariant
4. Secure Builder Pattern

สำหรับ Entity ที่ซับซ้อน ใช้ Builder ซ่อนอ็อบเจกต์ที่ยังสร้างไม่เสร็จไว้ ตรวจ checkInvariants() ในเมธอด build() แล้ว Self-destruct (account = null) ทันที

Rule: Self-Destructing Builder
03

แบบฝึกหัดปฏิบัติตามสถานการณ์จริง (Hands-on Refactoring)

Before: Insecure Entity (เสี่ยง State Corruption) No-arg, Unrestricted Setters & Leaky Collection
// โค้ดเดิม: สร้างวัตถุพิการได้, เปิด Setters อันตราย, และส่ง Collection ฉบับจริงออกไป
public class Order {
    private Long id;
    private CustomerId customerId;
    private Address shippingAddress;
    private List<OrderLine> items = new ArrayList<>();
    private String status;

    // ช่องโหว่ 1: No-arg constructor อนุญาตให้สร้างวัตถุพิการที่ยังไม่มีข้อมูล!
    public Order() {}

    public void setCustomerId(CustomerId customerId) { this.customerId = customerId; }
    public void setShippingAddress(Address address) { this.shippingAddress = address; }
    // ช่องโหว่ 2: Unrestricted Setter แอบเปลี่ยนสถานะข้ามกฎธุรกิจได้
    public void setStatus(String status) { this.status = status; }

    // ช่องโหว่ 3: Escaping reference! ภายนอกสั่ง clear() หรือ add() ได้เอง!
    public List<OrderLine> getItems() {
        return this.items;
    }
}
⚠️ จุดพังของโค้ด: เปิดให้สร้างอ็อบเจกต์โดยไม่มีที่อยู่จัดส่ง, ภายนอกสั่ง order.getItems().clear() ล้างสินค้าทิ้งได้เอง, และเปลี่ยนสถานะโดยไม่ผ่านกฎทางธุรกิจ
After: Secure Aggregate Root (การันตี State Integrity) Mandatory Constructor & Guarded Collections
import static org.apache.commons.lang3.Validate.*;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

public final class Order {
    private final OrderId id;           // Partially Immutable: ห้ามแก้หลังสร้าง
    private final CustomerId customerId;
    private final Address shippingAddress;
    private final List<OrderLine> items;
    private OrderStatus status;

    // 1. Mandatory Constructor: ข้อมูลครบถ้วนแต่วินาทีแรก (Consistent on Creation)
    public Order(OrderId id, CustomerId customerId, Address shippingAddress, List<OrderLine> initialItems) {
        this.id = notNull(id, "OrderId is required");
        this.customerId = notNull(customerId, "CustomerId is required");
        this.shippingAddress = notNull(shippingAddress, "ShippingAddress is required");
        notNull(initialItems, "Initial items required");
        isTrue(!initialItems.isEmpty(), "Order must contain at least one item");

        // Defensive Copy เพื่อตัดการเชื่อมโยงกับ List ภายนอก
        this.items = new ArrayList<>(initialItems);
        this.status = OrderStatus.CREATED;
    }

    // 2. Intent-Revealing Domain Operations แทน Unrestricted Setters
    public void cancel() {
        validState(this.status == OrderStatus.CREATED, 
            "Cannot cancel an order that is already processed");
        this.status = OrderStatus.CANCELLED;
    }

    public void addOrderLine(OrderLine item) {
        notNull(item, "Item cannot be null");
        validState(this.status == OrderStatus.CREATED, 
            "Cannot add items to a finalized order");
        this.items.add(item);
    }

    // 3. Guarded Collections: ส่งออกเฉพาะ Unmodifiable List ป้องกัน Escaping Reference
    public List<OrderLine> items() {
        return Collections.unmodifiableList(items);
    }
}
🛡️ จุดเปลี่ยนสำคัญ: โค้ดใหม่ไม่ยอมให้สร้างอ็อบเจกต์ที่ไร้ที่อยู่จัดส่ง, เปลี่ยนสถานะผ่านเมธอด cancel() ที่ตรวจสอบกฎธุรกิจ, และป้องกัน Collection ด้วย unmodifiableList ทำให้ภายนอกแอบแก้ไขข้อมูลไม่ได้ 100%!
04

ชุดควิซประเมินความรู้ประจำวัน

ตอบปุ๊บ รู้ผลเฉลยทันที พร้อมคำอธิบายและหลอดพลังงาน Energy Charge

Energy Charge
0 / 10
📌 CLOSING TITLE CONNECTION WRAP-UP สรุปหมุดเชื่อมโยงกลับหาหัวข้อบท

สรุปการเชื่อมโยงกลับสู่ Chapter 6 — Ensuring integrity of state

เรื่องราวของโศกนาฏกรรมสั่งซื้อสินค้าไร้ที่อยู่จัดส่ง, อันตรายของ Unrestricted Setters, และการป้องกัน Escaping Reference ใน Collection ทั้งหมดนี้ ล้วนเป็นคำตอบที่เด่นชัดของหัวข้อบท "Ensuring integrity of state" ว่า การรักษาความถูกต้องของสถานะไม่ใช่เรื่องของการติดแอนตี้ไวรัส แต่คือการออกแบบ Entity ให้ Consistent on Creation, ปิดกั้นการแอบแก้ไขสถานะจากภายนอกด้วย Intent-Revealing Domain Operations, และคุ้มกันข้อมูลภายในด้วย Guarded Collections เพื่อให้ระบบของเราทำงานได้อย่างถูกต้องและปลอดภัยในทุกสถานการณ์ครับ!

พร้อมก้าวสู่วันสุดท้ายของ Part 2 แล้วหรือยัง? Next: Day 7 — Chapter 7: Reducing complexity of state (State Objects, Snapshots & Relays) ➔