🥊
Phase 2: Deliberate Refactoring Katas

SECURE BY DESIGN DAY 16

เริ่มทำควิซสะสม 10 ข้อ ⚡
Phase 2: Deliberate Refactoring Katas | Kata 1 (Part 2)

Complete Domain Primitive Suite

New API Strangler Approach 🛡️
📌 CUMULATIVE SPACED REPETITION MAPPING เชื่อมต่อทฤษฎี Phase 1 สู่สมรภูมิ Refactoring จริง
10 Cumulative Review Quizzes

ภารกิจประจำวัน: วันนี้เราจะสกัด Domain Primitives อีก 4 ตัวที่เหลือ ได้แก่ Money, PostalAddress, ItemCode, และ DiscountCode เพื่อกำจัด Primitive Obsession ให้หมดสิ้น จากนั้นนำแนวทาง New API Approach (Day 12) มาปรับปรุง OrderProcessingService โดยเปิดเมธอดใหม่คู่ขนานไปกับ Legacy Bridge Method ที่ติด @Deprecated เพื่อให้มั่นใจว่าการปรับปรุงโค้ดจะไม่ส่งผลกระทบต่อระบบเดิมที่กำลังรันอยู่!

📉 Day 2: Anti-Hamlet Effect กำจัดตัวเลขติดลบและข้อมูลขยะที่ส่งผลให้ระบบคำนวณราคาหรือสต็อกสินค้าเพี้ยน
🧱 Day 5: Validation Pipeline สกัดสตริงและพารามิเตอร์ขยะตั้งแต่วินาทีแรกด้วย Valid on Creation
💰 Day 6: Partial Primitives สร้างคลาส Money ที่รวมทั้ง Amount และ Currency ป้องกันการบวกเงินข้ามสกุล
🔄 Day 12: New API Approach สร้าง Method Signature ใหม่คู่ขนานกับของเดิมแบบ Strangler Fig ไร้ Downtime
ความคืบหน้า Kata 1 (Part 1 Recap)

Day 15 ได้ทำ Code Audit วิเคราะห์ 4 รอยแตกร้าว และสกัด EmailAddress กับ Quantity เรียบร้อยแล้ว

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

Day 16 จาก 35 วัน — สกัด Domain Primitives ส่วนที่เหลือให้ครบชุด และเชื่อม Bridge สู่ OrderProcessingService

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

สร้าง Money, PostalAddress, ItemCode, DiscountCode และล้างแค้น Legacy Code ด้วย New API

02

การสกัด Domain Primitives ครบชุด (Completing the Suite)

💵 DOMAIN PRIMITIVE 3 • Money.java (Defeating Partial Primitives) Amount + Currency Conceptual Whole
import java.util.Currency;
import java.util.Objects;

public final class Money {
    private final double amount;
    private final Currency currency;

    public Money(double amount, Currency currency) {
        // Step 1 & 2: Origin & Bound Validation (ห้ามติดลบ และห้ามไร้สกุลเงิน)
        if (amount < 0) {
            throw new IllegalArgumentException("Amount cannot be negative. Provided: " + amount);
        }
        if (currency == null) {
            throw new IllegalArgumentException("Currency cannot be null");
        }
        this.amount = amount;
        this.currency = currency;
    }

    // Domain Operation: คูณจำนวนสินค้าอย่างปลอดภัย โดยรักษา Currency เดิมเสมอ
    public Money multiply(int quantity) {
        if (quantity <= 0) {
            throw new IllegalArgumentException("Quantity for multiplication must be positive");
        }
        return new Money(this.amount * quantity, this.currency);
    }

    public double amount() { return amount; }
    public Currency currency() { return currency; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return Double.compare(money.amount, amount) == 0 && Objects.equals(currency, money.currency);
    }

    @Override
    public int hashCode() { return Objects.hash(amount, currency); }

    @Override
    public String toString() { return String.format("%.2f %s", amount, currency.getCurrencyCode()); }
}
🏷️ DOMAIN PRIMITIVE 4 • ItemCode.java Syntax Boundary
public final class ItemCode {
    private static final String ITEM_CODE_REGEX = "^ITEM-[A-Z0-9]{5,10}$";
    private final String value;

    public ItemCode(String rawCode) {
        if (rawCode == null || rawCode.isBlank()) {
            throw new IllegalArgumentException("Item code cannot be null or blank");
        }
        String trimmed = rawCode.trim().toUpperCase();
        if (!trimmed.matches(ITEM_CODE_REGEX)) {
            throw new IllegalArgumentException("Invalid Item Code format: " + rawCode);
        }
        this.value = trimmed;
    }

    public String value() { return value; }

    @Override
    public String toString() { return value; }
}
🎟️ DOMAIN PRIMITIVE 5 • DiscountCode.java Safe Default State
public final class DiscountCode {
    private static final String DISCOUNT_REGEX = "^[A-Z0-9]{4,8}$";
    private final String code;

    public DiscountCode(String rawCode) {
        // Safe Default: อนุญาตให้เป็น null/blank ได้โดยจะแทนค่าด้วย "NONE"
        if (rawCode == null || rawCode.isBlank()) {
            this.code = "NONE";
            return;
        }
        String trimmed = rawCode.trim().toUpperCase();
        if (!trimmed.matches(DISCOUNT_REGEX)) {
            throw new IllegalArgumentException("Invalid Discount Code syntax");
        }
        this.code = trimmed;
    }

    public String code() { return code; }

    @Override
    public String toString() { return code; }
}
🏠 DOMAIN PRIMITIVE 6 • PostalAddress.java (Whole Value Object) Bound Validation & Normalization
public final class PostalAddress {
    private final String addressLine;

    public PostalAddress(String rawAddress) {
        if (rawAddress == null || rawAddress.isBlank()) {
            throw new IllegalArgumentException("Shipping address cannot be null or blank");
        }
        String trimmed = rawAddress.trim();
        if (trimmed.length() < 5 || trimmed.length() > 250) {
            throw new IllegalArgumentException("Address length must be between 5 and 250 characters");
        }
        this.addressLine = trimmed;
    }

    public String value() { return addressLine; }

    @Override
    public String toString() { return addressLine; }
}
🛡️ REFACTORED SERVICE • OrderProcessingService.java (New API Approach & Strangler Bridge) Zero Parameter Swapping Risk
import java.util.Currency;

public class OrderProcessingService {

    // ✅ New API Approach: ใช้ Domain Primitives ครบทั้งชุด การันตี Compile-Time Safety 100%!
    // ไม่สามารถส่ง String สลับตำแหน่ง หรือส่ง Quantity/Money ติดลบเข้ามาได้อีกต่อไป
    public void processOrder(EmailAddress customerEmail,
                             PostalAddress shippingAddress,
                             ItemCode itemCode,
                             Quantity quantity,
                             Money unitPrice,
                             DiscountCode discountCode) {

        // คำนวณราคารวมผ่าน Domain Primitives ปราศจากการใช้ Primitive Ops ติดลบ
        Money totalAmount = unitPrice.multiply(quantity.value());

        System.out.println("Processing secure order for: " + customerEmail.value() +
                           " Item: " + itemCode.value() +
                           " Total: " + totalAmount);
    }

    // 🔄 Legacy Bridge Method (Deprecated): ใช้สำหรับรองรับผู้เรียกใช้เก่า แล้วแปลงเข้า Domain Primitives ทันที
    // หากผู้เรียกเก่าส่ง quantity = -5 หรือ email ผิดรูปแบบ Constructor ของ Domain Primitive จะ Fail-Fast ทันที!
    @Deprecated
    public void processOrder(String customerEmail, String shippingAddress, String itemCode,
                             int quantity, double unitPrice, String discountCode) {
        processOrder(
            new EmailAddress(customerEmail),
            new PostalAddress(shippingAddress),
            new ItemCode(itemCode),
            new Quantity(quantity),
            new Money(unitPrice, Currency.getInstance("USD")),
            new DiscountCode(discountCode)
        );
    }
}
🎯 สถาปัตยกรรม Strangler Fig: ผู้เรียกใช้เดิมยังคงเรียกใช้เมธอดเดิมได้โดยไม่เกิด Compile Break แต่ภายในจะถูกบังคับแปลงเข้าสู่ Domain Primitives ทันที หากมีข้อมูลขยะหรือค่าติดลบ ระบบจะโยน Exception ตั้งแต่หน้าประตู (Fail-Fast & Fail-Secure)
03

จุดเปลี่ยนสำคัญทางสถาปัตยกรรม (Architectural Trophies)

🛡️ Zero Swapping Risk

ไม่มีพารามิเตอร์ประเภท String ติดกันอีกต่อไป คอมไพเลอร์บังคับประเภทข้อมูลอย่างเข้มงวด หากสลับที่อยู่กับอีเมลจะ Compile Error ทันที

📉 Anti-Hamlet Shielded

ทั้ง Quantity และ Money ล็อก Invariants ห้ามค่าติดลบและศูนย์ ทำให้ผลการคำนวณราคารวมไม่มีวันติดลบได้อีก

🧹 No Defensive Checks

เมธอดชั้นในตัด if (email == null) ทิ้งได้ 100% เพราะ Domain Primitives รับประกันความสมบูรณ์ตั้งแต่วินาทีที่สร้าง

🔄 Strangler Safety

ใช้ New API Approach ควบคู่กับ @Deprecated ทำให้ระบบเดิมทำงานต่อเนื่องได้โดยไร้ Downtime พร้อมค่อยๆ ย้ายโค้ดอย่างปลอดภัย

04

ชุดควิซประเมินความรู้สะสมประจำวัน (Day 16 Cumulative Quiz - 10 ข้อ)

30% ทบทวนความรู้ Phase 1 (Days 2, 6, 9, 12) + 70% เจาะลึกการ Refactoring Day 16 ตอบปุ๊บ รู้ผลเฉลยทันทีพร้อมคำอธิบาย

Kata Energy
0 / 10
📌 CLOSING SUMMARY & DAY 17 PREVIEW สรุปผลการปฏิบัติตาม Kata 1 (Part 2)

สรุปปิดท้าย Day 16 — Kata 1: Primitive Obsession Breakdown (Part 2)

ในวันนี้เราได้สกัด Domain Primitives ครบทั้งชุด (Money, PostalAddress, ItemCode, DiscountCode) และนำแนวทาง New API Approach มาปรับปรุง OrderProcessingService โดยเชื่อม Legacy Bridge Method ไว้ ทำให้ระบบเก่าทำงานได้อย่างปลอดภัย ไร้ Parameter Swapping และป้องกันค่าติดลบได้อย่างถอนรากถอนโคน! ควิซทั้ง 10 ข้อได้เชื่อมโยงทฤษฎีจาก Phase 1 (Day 2 Anti-Hamlet, Day 6 Partial Primitives, Day 9 Fail-Secure, Day 12 New API) เข้ากับโค้ดจริงได้อย่างสมบูรณ์แบบ

พรุ่งนี้ใน Day 17 เราจะเข้าสู่การสร้าง Validation Pipeline 5 ขั้นแบบลึกซึ้ง และการจัดการ Custom Security Exceptions เพื่อให้ระบบการรายงานข้อผิดพลาดมีความปลอดภัยสูงสุด (Zero Technical Leakage) ครับ! Next: Day 17 — Kata 1 (Part 3: 5-Stage Validation Pipeline & Secure Exception Hierarchy) ➔