ข้าม​ไป​ยัง​เนื้อหา

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 คือ “สมาชิก​ใหม่” ที่​ส่ง​ผลกระทบ​ต่อ​สถาปัตยกรรม​องค์กร​อย่าง​มาก

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 นิยามด้วยค่า ไม่มี identity
public sealed record Location(double Latitude, double Longitude);
public sealed record PackageWeight(decimal Kilograms)
{
public PackageWeight
{
if (Kilograms <= 0)
throw new ArgumentException("น้ำหนักพัสดุต้องมากกว่าศูนย์");
}
}
// Domain Event: สิ่งที่เกิดขึ้นแล้วใน domain ระบุด้วยรูปกริยาช่องที่ 3
public sealed record DeliveryCompleted(Guid DeliveryId, DateTimeOffset CompletedAtUtc);
// Entity: มี identity ที่ติดตามได้ พฤติกรรมอยู่ในตัวมันเอง ไม่ใช่ anemic
public 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