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)
| # | Consumer | Topic | → Dispatch |
|---|---|---|---|
| C1 | FlowSubmittedConsumer | flow-submitted-for-approval | SubmitEvent (first/resubmit) [G-1] |
| C2 | FlowCancelRequestedConsumer | flow-cancel-requested | CancelEvent(initiatedBy=Lego) |
| C3 | WorkflowPickupConsumer | workflow-pickup | PickupEvent |
| C4 | WorkflowSentToReviewerConsumer | workflow-sent-to-reviewer | SentToReviewerEvent |
| C5 | WorkflowDecisionConsumer | workflow-decision | DecisionEvent(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
| # | Port | Impl | Transport | ใช้ตอน |
|---|---|---|---|---|
| P1 | ILegoPort | AsbLegoAdapter | ASB | publish flow-progress-update (S3) + flow-approval-result (decision) |
| P2 | IWorkflowPort | HttpWorkflowAdapter | HTTP | create (GET-by-document ก่อน POST = idempotency) / resubmit / force-stop |
| P3 | ITaskPort | HttpTaskAdapter | HTTP | create (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 |
|---|---|
TaskCreationRequested | P3 create task |
WorkflowCreationRequested | P2 create workflow |
LegoProgressRequested (ใหม่) | P1 publish progress |
LegoTransitionRequested | P1 publish decision (approval-result) |
TaskCompleteRequested / TaskCloseRequested (ใหม่/มี) | P3 complete/close |
TaskReworkRequested | P3 rework |
WorkflowResubmitRequested | P2 resubmit |
WorkflowForceStopRequested | P2 force-stop |
ตัดออก (grill notification ownership):
,MakersNotificationRequested— orchestrator ไม่ raise/handle notification event แล้ว; maker pool/assignee notify = Workflow ยิงเองตอน create/resubmit (ดู 03); requestor notify = Lego ยิงเอง (ดู 01 §L10)AdminNotificationRequested
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_ONBOARD→NEW_REQUESTOR?