Tactical Design
แปลง model เชิงแนวคิดให้เป็น code ที่จับต้องได้
Tactical Design เริ่มจาก model domain เชิงแนวคิดที่ได้จากเฟส strategic แล้วลงลึกสู่ รายละเอียดการ implement ระดับล่าง — เปลี่ยนแนวคิดให้กลายเป็นรูปธรรม
เป้าหมาย: แปล strategic design ให้เป็น class interface และ module ที่จับต้องได้, ประยุกต์ tactical design pattern เพื่อ optimize model และทำให้ model ถูก implement อย่างมีประสิทธิภาพ กิจกรรมในเฟสนี้ได้แก่ การออกแบบ entity และ value object, การระบุ aggregate, การนิยาม domain service และการ implement รายละเอียด specification/validation
คำว่า “tactical” มาจาก Eric Evans เองในหนังสือ Domain-Driven Design (2003) ซึ่งแบ่งเนื้อหาออกเป็นสองระดับคือ strategic design (จัดการ bounded context และความสัมพันธ์ระหว่าง context) กับ tactical design (ชุด pattern สำหรับ implement model ภายใน context เดียว) Vaughn Vernon ใน Implementing Domain-Driven Design (IDDD) ขยายชุด building block ของ tactical DDD ให้ครบถ้วนขึ้น โดยระบุว่าเครื่องมือ tactical modeling หลักมี Entity, Value Object, Domain Service, Domain Event, Module, Aggregate, Factory และ Repository — และเน้นว่า Domain Event คือ “สมาชิกใหม่” ที่ส่งผลกระทบต่อสถาปัตยกรรมองค์กรอย่างมาก
บทบาทใน DDD
หัวข้อที่มีชื่อว่า “บทบาทใน DDD”Tactical design ทำงานคู่กับ strategic design เสมอ ไม่ใช่ทางเลือกที่แยกจากกัน โดยมีจุดต่างที่ชัดเจน
- Strategic design ตอบคำถามระดับกว้าง เช่น ระบบควรถูกแบ่งเป็น context อะไรบ้าง, context ไหนคุยกับ context ไหน, ใครคือ upstream/downstream — ผลลัพธ์คือ Context Map
- Tactical design ตอบคำถามระดับ code เช่น class นี้ควรเป็น entity หรือ value object, invariant อะไรต้อง enforce ภายใน transaction เดียว, business rule ที่ไม่ผูกกับ object ใดควรอยู่ที่ไหน — ผลลัพธ์คือ code ที่ compile และรันได้จริง
ตาม Microsoft Learn (Azure Architecture Center) หลักการสำคัญคือ ออกแบบ microservice ให้ไม่เล็กไปกว่า aggregate และไม่ใหญ่ไปกว่า bounded context — นี่คือจุดที่ tactical กับ strategic บรรจบกัน เพราะ aggregate (ผลผลิตของ tactical design) กลายเป็นหน่วยที่กำหนดขอบเขต service ในสถาปัตยกรรม microservice
Tactical design ยังเป็นแนวป้องกัน anemic domain model (anti-pattern) ด้วย เพราะหลักการของมันบังคับให้ business logic — validation, state transition, invariant — อยู่ ภายใน entity/value object แทนที่จะกระจายออกไปอยู่ใน service classes ที่ทำหน้าที่แค่ orchestration
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”สมมติ domain ระบบส่งของด้วยโดรน (drone delivery) จาก bounded context “Shipping” เราจะเห็น tactical building block ทำงานร่วมกันดังนี้
- Entity:
Delivery,Package,Drone— แต่ละตัวมี identity ที่ติดตามได้ตลอดวงจรชีวิต - Value Object:
Location,ETA,PackageWeight— ไม่มี identity นิยามด้วยค่าของ attribute เท่านั้น immutable - Aggregate:
Deliveryเป็น aggregate root ที่ครอบConfirmationและNotification(เป็น entity ลูกที่ไม่มีชีวิตอิสระ) ส่วนDroneและPackageเป็น aggregate แยกต่างหาก อ้างอิงกันด้วย ID เท่านั้น ไม่ใช่ object reference ตรง - Domain Service:
Schedulerทำหน้าที่คำนวณตารางส่งของ — logic ที่พาดผ่านหลาย aggregate จึงไม่ควรอยู่ใน entity ใดตัวหนึ่ง - Domain Event: เมื่อ
Deliveryเปลี่ยนสถานะ จะ raise event เช่นDeliveryCompletedเพื่อให้ bounded context อื่น (เช่น Accounts) ตอบสนองแบบ eventual consistency
code C# ด้านล่างแสดงโครงสร้างขั้นต่ำของ tactical building block เหล่านี้ในบริบทเดียวกัน
// Value Object: immutable นิยามด้วยค่า ไม่มี identitypublic sealed record Location(double Latitude, double Longitude);
public sealed record PackageWeight(decimal Kilograms){ public PackageWeight { if (Kilograms <= 0) throw new ArgumentException("น้ำหนักพัสดุต้องมากกว่าศูนย์"); }}
// Domain Event: สิ่งที่เกิดขึ้นแล้วใน domain ระบุด้วยรูปกริยาช่องที่ 3public sealed record DeliveryCompleted(Guid DeliveryId, DateTimeOffset CompletedAtUtc);
// Entity: มี identity ที่ติดตามได้ พฤติกรรมอยู่ในตัวมันเอง ไม่ใช่ anemicpublic sealed class Delivery{ public Guid Id { get; } public Guid DroneId { get; private set; } public Guid PackageId { get; private set; } public Location Pickup { get; private set; } public Location DropOff { get; private set; } public DeliveryStatus Status { get; private set; }
private readonly List<object> _domainEvents = new(); public IReadOnlyList<object> DomainEvents => _domainEvents;
public Delivery(Guid id, Guid droneId, Guid packageId, Location pickup, Location dropOff) { Id = id; DroneId = droneId; PackageId = packageId; Pickup = pickup; DropOff = dropOff; Status = DeliveryStatus.Scheduled; }
// Business rule อยู่ในตัว entity เอง ไม่ใช่ใน service ภายนอก public void Complete() { if (Status != DeliveryStatus.InTransit) throw new InvalidOperationException("ปิดงานส่งของได้เฉพาะที่กำลังขนส่งอยู่เท่านั้น");
Status = DeliveryStatus.Completed; _domainEvents.Add(new DeliveryCompleted(Id, DateTimeOffset.UtcNow)); }}
public enum DeliveryStatus { Scheduled, InTransit, Completed, Cancelled }
// Domain Service: logic ที่พาดผ่านหลาย aggregate ไม่ควรฝังไว้ใน entity ใดตัวหนึ่งpublic interface IDeliveryScheduler{ Delivery ScheduleDelivery(Guid packageId, Location pickup, Location dropOff);}ความสัมพันธ์กับแนวคิดอื่น
หัวข้อที่มีชื่อว่า “ความสัมพันธ์กับแนวคิดอื่น”graph TD Strategic[Strategic Design] --> Tactical[Tactical Design] Tactical --> Entity[Entity] Tactical --> ValueObject[Value Object] Tactical --> Aggregate[Aggregate] Tactical --> DomainService[Domain Service] Tactical --> DomainEvent[Domain Event] Aggregate --o Entity Aggregate --o ValueObject Tactical -.-> Anemic[Anemic Domain Model] BoundedContext[Bounded Context] --> Tactical
- Tactical design สืบทอด ขอบเขตจาก Bounded Context ที่ strategic design กำหนดไว้ — ทำงานภายใน1 context เสมอ ไม่ข้าม context
- Aggregate เป็น building block ที่สำคัญที่สุดของ tactical design เพราะกำหนดขอบเขต transaction/consistency และมัก map ตรงกับขอบเขต microservice
- Tactical design ที่ทำถูกต้องจะ ป้องกัน Anemic Domain Model เพราะบังคับให้ business rule อยู่ในตัว entity/aggregate ไม่ใช่กระจายอยู่ใน service ชั้นนอก
- Domain Service ต่างจาก Application Service: domain service มี business logic ที่พาดผ่านหลาย aggregate ส่วน application service ทำหน้าที่ orchestrate use case (transaction, auth, notification) โดยไม่มี business logic ของตัวเอง
- Pattern เชิง tactical จำนวนมาก เช่น Specification และ Repository ถูกใช้ร่วมกับ entity/aggregate เพื่อ query และ persist model domain โดยไม่ให้ domain layer รู้จัก infrastructure