Private Docs

02 — OrchestratorService — Work Breakdown

งานฝั่ง Orchestrator = Lane 2 (adapters) ทั้งหมด + ต่อเติม saga (Lane 1 engine core + saga สร้างเสร็จแล้ว)

อัปเดต: 2026-06-15

อ่าน 00-OVERVIEW.md ก่อน — ไฟล์นี้สำหรับ dev ฝั่ง Orchestrator ฐาน: Lane 1 (engine core + saga) สร้างเสร็จแล้ว — งานนี้ = Lane 2 (adapters) ทั้งหมด + ต่อเติม saga อ้างอิง: 09062026/ORCHESTRATION_LANE2_ADAPTERS.md + ADR 0031-0033


บทบาทของ Orchestrator = control plane

  • Consume (inbound): flow-submitted, flow-cancel-requested (จาก Lego) · Pickup, SentToReviewer, Decision, Cancel (จาก Workflow)
  • Saga ProcessState = source of truth ของ in-flight; track 2 identity (MakerUsername + ReviewerUsername)
  • Produce (outbound ผ่าน ports): progress + decision → Lego · create/rework/close → Task · create/resubmit/force-stop → Workflow
  • ไม่ทำ notification เลย (grill 2026-06-15) — Lego→requestor, Workflow→maker ยิงเอง

A. Saga ต่อเติม (Lane 1 — OnboardingApprovalSaga.cs)

S1 — ProcessState ใหม่

OnboardingProcessState.UnderConsideration (map กับ Lego Reviewed)

S2 — Inbound event ใหม่

SentToReviewerEvent : IOrchestrationEvent (Workflow→Orch) ใน Orchestrator04.Domain/Orchestration/Messaging/InboundEvents.cs

S3 — Outbox event ใหม่

LegoProgressRequested(Guid flowInstanceId, string progressStatus, string? maker, DateTime occurredAt) ใน OutboxEvents.cs → ยิงเข้า topic flow-progress-update

S4 — แก้ handlers

Handlerแก้
OnPickup (
)
track MakerUsername (2-actor) + Raise(LegoProgressRequested(fid,"InReview",p.OccurredAt)) (เดิมไม่ raise กลับ Lego)
OnSentToReviewer (ใหม่)ExpectState(UnderReview) → track ReviewerUsername + Raise(LegoProgressRequested(fid,"Reviewed",...)) → Transition(UnderConsideration)
OnDecision (
)
4-eye gate (grill): Approve รับเฉพาะ UnderConsideration (Reviewer); Reject/Rework รับ {UnderReview, UnderConsideration} (Maker kick-back ได้); Approve → +TaskCompleteRequested; Reject → +TaskCloseRequested (กัน task ค้าง)
OnResubmit (
)
คง WorkflowResubmit + TaskRework (ตัด AdminNotify — Workflow notify maker เอง); ลบ assumption “FlowInstanceId per-submission overwrite” (D9 — instance เดิม, stable)
OnCancel (
)
teardown TaskClose + WorkflowForceStop + LegoTransition(Cancel); idempotent (ignore ถ้า Cancelled แล้ว); ไม่ notify

2-actor data: OnboardingData ขยายจาก field เดียว → { MakerUsername, ReviewerUsername } (jsonb scratch)

S5 — แก้ OwnedFlowCodes ⚠️

OnboardingApprovalSaga.cs:29 ปัจจุบัน {"FX_ONBOARD"}verify + แก้เป็น flow code จริง (NEW_REQUESTOR ฯลฯ) + ยืนยัน disjoint จาก AutoApproveWorker whitelist (STANDARD_CUSTOMER_ONBOARDING/CUSTOMER_FIXED_ONBOARDING) — Phase-0


B. Lane 2 — Inbound consumers (ASB)

#ConsumerTopic→ Dispatch
C1FlowSubmittedConsumerflow-submitted-for-approvalSubmitEvent (first/resubmit) [G-1]
C2FlowCancelRequestedConsumerflow-cancel-requestedCancelEvent(initiatedBy=Lego)
C3WorkflowPickupConsumerworkflow-pickupPickupEvent
C4WorkflowSentToReviewerConsumerworkflow-sent-to-reviewerSentToReviewerEvent
C5WorkflowDecisionConsumerworkflow-decisionDecisionEvent(Approve/Reject/Rework/Cancel)
  • ทุกตัวผ่าน OrchestrationDispatcher เดิม (dedup MessageId → correlate RefNo → resolve saga → dispatch → persist atomic)
  • pattern: BackgroundService + ServiceBusProcessor (Outbox reusable มีใน Backend_Package แล้ว — ไม่ต้องเขียนใหม่)

C. Lane 2 — Outbound ports + adapters

#PortImplTransportใช้ตอน
P1ILegoPortAsbLegoAdapterASBpublish flow-progress-update (S3) + flow-approval-result (decision)
P2IWorkflowPortHttpWorkflowAdapterHTTPcreate (GET-by-document ก่อน POST = idempotency) / resubmit / force-stop
P3ITaskPortHttpTaskAdapterHTTPcreate (skip ถ้า Correlations.TaskId มี) / rework / close / complete

ตัดออก (grill notification ownership): P4 INotifyPort, P5 IMakerDirectoryPort — orchestrator ไม่ทำ notification; ไม่ต้อง resolve maker → ตัด Phase-0 dep “maker role name” idempotency (decision C): Task/Workflow create → ดู Correlations.TaskId/WfInstanceId ก่อน ถ้ามีแล้ว skip; teardown ต้องทน “target ยังไม่ถูกสร้าง” (cancel-vs-create race) Workflow/Task adapter (M3-M5, M9-M14) = 🤝 contract ต้องคุยกับทีมเจ้าของ — โดยเฉพาะ Workflow แปลง native (APPROVE/REJECT/OnRejectToPhaseOrder) → Decision vocab ที่ publisher ฝั่ง Workflow


D. Lane 2 — Outbox handlers (bridge saga events → ports) [G-3]

ทุก outbox event ที่ saga raise ต้องมี handler:

Outbox event→ Port action
TaskCreationRequestedP3 create task
WorkflowCreationRequestedP2 create workflow
LegoProgressRequested (ใหม่)P1 publish progress
LegoTransitionRequestedP1 publish decision (approval-result)
TaskCompleteRequested / TaskCloseRequested (ใหม่/มี)P3 complete/close
TaskReworkRequestedP3 rework
WorkflowResubmitRequestedP2 resubmit
WorkflowForceStopRequestedP2 force-stop

ตัดออก (grill notification ownership): MakersNotificationRequested, AdminNotificationRequested — orchestrator ไม่ raise/handle notification event แล้ว; maker pool/assignee notify = Workflow ยิงเองตอน create/resubmit (ดู 03); requestor notify = Lego ยิงเอง (ดู 01 §L10)


Test checklist

  • OnPickup → track Maker + raise Progress(InReview)
  • OnSentToReviewer → track Reviewer + Progress(Reviewed) + UnderConsideration
  • 4-eye: OnDecision Approve จาก UnderReview (Maker) → rejected/ignored; จาก UnderConsideration → ok + TaskComplete
  • OnDecision Reject/Rework รับจากทั้ง {UnderReview, UnderConsideration}; Reject → +TaskClose
  • Decision-before-pickup (AwaitingPickup) → ignored
  • OnCancel idempotent (double-sweep no-op)
  • OwnedFlowCodes มี flow code จริง + disjoint AutoApprove
  • Port adapters: idempotent create (skip ถ้า Correlations มี), teardown ทน not-yet-created
  • Dispatcher dedup (MessageId ซ้ำ)

Phase-0 ต้อง ratify

  • Lego↔Orch contract (M1, M2, M6, M7) — lock ได้เลย (user เป็นเจ้าของ)
  • Workflow↔Orch + Task↔Orch (M3-M5, M9-M14) 🤝 — คุยกับทีม Workflow/Task (รวม native→Decision mapping + pickup → ตัดสิน InReview)
  • OwnedFlowCodes จริง (S5) — FX_ONBOARDNEW_REQUESTOR?