บทที่ 24 · Part 5 — From Model to Running System

Application Boundaries and Transactions

รักษา domain decisions ภายใน model และให้ application layer ประสาน use case, transaction, authorization และ ports

Application Boundaries and Transactions

Domain Model ที่ดีอาจถูกทำลายได้ถ้า HTTP handler ตัดสิน policy, repository ส่ง ORM entity ให้ caller แก้ และ transaction commit ก่อน outbox Application boundary ทำให้ use case มีจุดประสาน actor, authorization, model, persistence และ external ports โดยไม่แย่ง business decision จาก domain

จบบทนี้คุณจะ

แยก driving adapter, Application Service และ domain model วาง authorization กับ transaction ที่เหมาะสม ออกแบบ command/result ที่ใช้ Ubiquitous Language และรักษา Domain Model จาก HTTP, JWT, SQL กับ vendor concerns โดยไม่ต้องเลือก architecture template ทั้งระบบ

Application Service เป็น Use-case Boundary

ตัวอย่าง ConfirmTransfer:

func (s Service) ConfirmTransfer(ctx context.Context, cmd ConfirmTransfer) error {
	if err := s.authorizer.CanConfirm(ctx, cmd.Principal, cmd.IntentID); err != nil {
		return err
	}

	return s.tx.Within(ctx, func(ctx context.Context) error {
		intent, err := s.intents.ByID(ctx, cmd.IntentID)
		if err != nil {
			return err
		}
		if err := intent.Confirm(cmd.TermsVersion); err != nil {
			return err
		}
		return s.intents.Save(ctx, intent)
	})
}

Application Service orchestrates แต่ intent.Confirm ปกป้อง invariant

Driving Adapter แปล Transport

HTTP/CLI/message adapter:

  • parse/shape validation
  • authenticate และสร้าง Principal
  • translate DTO เป็น application command
  • call use case
  • map errors/result เป็น protocol

Domain ไม่ parse JWT/header/status code และไม่รู้ framework

Authorization หลายชั้น

แยก:

LayerDecision
Edge/adapteridentity/token authenticity
Applicationactor ใช้ use case/tenant/resource ได้ไหม
Domainaction ถูก policy/invariant ณ state นี้ไหม
Datatenant/filter/grant ป้องกัน bypass
Auditevidence ใครตัดสิน/ทำอะไร

Domain rule maker cannot approve own request อาจอยู่ model/policy แต่ Principal mapping อยู่ adapter

Authorization Policy ต้องมี Owner

role, relationship, amount-based approval และ segregation rule ต้องมาจาก accountable Security/Risk/ Business owner พร้อม source/date Protocol authentication ไม่ตัดสิน domain policy แทน

Transaction Boundary

Local transaction ครอบ changes ที่ต้อง atomic ตาม Aggregate/use case แต่ห้ามเปิด database transaction ค้างระหว่าง network call

Pattern:

Transaction A: record intent/outbox
External call: provider with stable identity/idempotency
Transaction B: record observed outcome
Repair loop: reconcile unknown/crash

Business transaction ยาวกว่าหนึ่ง ACID transaction ต้องมี states/identity/recovery

Ports ตาม Purpose

Application-owned contracts เช่น:

type PaymentExecutor interface {
	Execute(ctx context.Context, request ExecutionRequest) (ExecutionOutcome, error)
}

Outcome แยก declined, rejected, indeterminate, accepted ไม่คืน vendor DTO/HTTP error ตรง ๆ Port ใช้เมื่อปกป้อง meaningful boundary ไม่สร้าง interface ทุก function เพื่อ mocking

Command และ Result

Command สื่อ intent มี business identity และ caller context ไม่ใช่ HTTP payload copy:

ConfirmTransfer{IntentID, Principal, TermsVersion, RequestID}

Result สื่อ outcome ที่ caller ใช้ได้ แยก domain rejection จาก technical failure/unknown

แบบฝึกปฏิบัติ: Confirm Transfer Boundary

วาดและเขียน skeleton:

HTTP DTO -> Principal/Command -> Application Service -> Aggregate -> Repository
                                      |
                                      -> Outbox/Port after correct boundary

ระบุ validation/authorization/domain decision/transaction/error owner และทดสอบ duplicate command, version conflict, unauthorized principal, outbox failure

รายการตรวจสอบ

  • Adapter แปล protocol และสร้าง Principal
  • Application Service orchestrates ไม่ถือ hidden domain rule
  • Domain model ไม่รู้ HTTP/JWT/SQL/vendor
  • Transaction ครอบ atomic local changes
  • Network call ไม่อยู่ใน long DB transaction
  • Port/result ใช้ application language
  • Authorization/domain policy/data isolation แยกชั้น

สรุปบทนี้

Application boundary เชื่อม model กับโลกจริงโดยรักษา responsibility: adapter จัด protocol, application ประสาน use case/transaction, domain ตัดสิน business behavior และ ports แปล external capabilities นี่เป็น consequence ของ model boundary ไม่ใช่เป้าหมาย DDD เอง

อ่านเพิ่มเติม