Backend Process — จาก API ถึง Database
เส้นทางของ 1 request ใน Lego Engine v2: Controller → MediatR pipeline → FlowEngine → Repository → EF Core → PostgreSQL พร้อม schema และ error model
อัปเดต: 2026-06-04
ทุก request ที่เข้า Lego Engine วิ่งผ่าน Clean Architecture 4 ชั้น เอกสารนี้ไล่ตั้งแต่ HTTP เข้ามาจน commit ลง PostgreSQL — ที่อยู่ของข้อมูล และ ท่อที่ data เดินผ่าน
สถาปัตยกรรม 4 ชั้น
UserService แบ่งเป็น 4 โปรเจกต์ตามทิศทาง dependency (ชั้นในไม่รู้จักชั้นนอก):
04 · Domain (Entities · Ports/interfaces) เป็นแกนใน — Application & Infrastructure อ้างเข้าหา ไม่ใช่ทางกลับ
| ชั้น | หน้าที่ | ตัวอย่างใน Lego |
|---|---|---|
| 01.API | รับ HTTP, auth, แปลง Result → HTTP | OnboardingV2Controller, GlobalExceptionHandler |
| 03.Application | business orchestration | ExecuteStepActionHandler, FlowEngine, step handlers |
| 02.Infrastructure | persistence จริง | FlowInstanceRepository, AppDbContext, EF config |
| 04.Domain | กฎ + interface (ports) | FlowInstance, IFlowInstanceRepository, IFlowStepHandler |
เส้นทางของ 1 request (ตัวอย่าง: กด “ถัดไป” ที่ step หนึ่ง)
Endpoint: POST /api/user-service/v1/onboarding/instances/{id}/steps/{stepType}/actions/{action}
sequenceDiagram
autonumber
participant FE as Frontend
participant C as OnboardingV2Controller
participant M as MediatR pipeline
participant H as ExecuteStepActionHandler
participant E as FlowEngine
participant SH as Step Handler
participant R as Repository (EF Core)
participant DB as PostgreSQL
FE->>C: POST .../actions/Next { stepData }
C->>M: Send(ExecuteStepActionCommand)
M->>H: behaviors (tx, validate, audit...)
H->>H: FlowAccessGuard (owner / EditSession cookie)
H->>E: ExecuteActionAsync(instance, stepType, action, json)
E->>SH: handler.ExecuteAsync(ctx)
SH-->>E: FlowStepExecutionResult.Ok(output)
E->>R: UpsertAsync(FlowInstanceStepData) + UpdateAsync(instance)
R->>DB: INSERT/UPDATE (commit ใน TransactionBehavior)
E-->>H: StepNavigationV2Dto (currentStep ถัดไป)
H-->>C: Result.Success(nav)
C-->>FE: 200 ApiOk(nav)
MediatR pipeline (behaviors)
ก่อนถึง handler จริง command วิ่งผ่าน behaviors เรียงชั้น (จากนอกสุดเข้าใน):
- TransactionBehavior เปิด/commit/rollback transaction รอบ handler (เฉพาะ command) — handler สำเร็จ = commit, throw = rollback แล้ว re-throw
- ValidationBehavior รัน FluentValidation ก่อนเข้า handler
- AuditBehavior / LoggingBehavior บันทึก request/ผลลัพธ์ · EncryptionBehavior ถอด/เข้ารหัส field ที่ติด
[Encrypt]
FlowEngine — หัวใจ runtime
FlowEngine.ExecuteActionAsync คือจุดที่ “ขยับ flow”:
- deserialize Snapshot จาก
instance.FlowSnapshotJson→ หา step ปัจจุบัน Back→ ถอยไป interactive step ก่อนหน้า- resolve Handler จาก
stepType→handler.ExecuteAsync(ctx) - ถ้า
!result.Success→throw FlowEngineException(errorCode, message) UpsertAsync(FlowInstanceStepData)เก็บ output ของ stepNext→ เลื่อนไป interactive step ถัดไป แล้วAdvanceAutomaticAsync(รัน Automatic step ที่คั่นจน finalize หรือเจอ interactive)UpdateAsync(instance)→BuildNavigationAsyncคืน nav ให้ FE
:::details ตัวอย่างโค้ด ExecuteActionAsync (ย่อ)
var handler = handlerRegistry.Resolve(stepType);
var result = await handler.ExecuteAsync(new FlowStepExecutionContext
{ Instance = instance, StepType = stepType, InputJson = inputJson, StepData = existing, ActionCode = action }, ct);
if (!result.Success)
throw new FlowEngineException(result.ErrorCode ?? "Step.Failed", result.ErrorMessage);
await stepDataRepo.UpsertAsync(
FlowInstanceStepData.CreateExecuted(instance.Id, stepType, result.OutputJson ?? inputJson, actor), ct);
// Next → advance + run automatic chain
var next = FlowSnapshotNavigator.NextInteractiveIndex(snapshot, curIdx);
if (next >= 0) instance.AdvanceTo(next, actor); else instance.AdvanceTo(curIdx + 1, actor);
await AdvanceAutomaticAsync(instance, snapshot, actor, ct);
await instanceRepo.UpdateAsync(instance, ct);
return await BuildNavigationAsync(instance, snapshot, ct);
:::
Database schema
ทุกตารางอยู่ใน PostgreSQL ผ่าน EF Core. FlowInstance เป็นศูนย์กลาง — ผูกกับ FlowDefinition (ผ่าน Snapshot), เก็บ output แต่ละ step ใน FlowInstanceStepData, มี EditSession/InvitationToken/CorrectionRequest ประกอบ:
| ตาราง | เก็บอะไร | หมายเหตุ |
|---|---|---|
FlowDefinitions | template flow (admin config) | + FlowSteps ลำดับ step |
StepCatalog | ทะเบียน step type ที่ใช้ได้ | ExecutionMode, SkipScope, DependenciesJson |
FlowInstances | การรัน 1 ครั้ง | Status, CurrentStepIndex, FlowSnapshotJson, OwnerUserId |
FlowInstanceStepData | output ของแต่ละ step | คอลัมน์ DataJson เป็น jsonb |
EditSessions | ผูก anonymous browser ↔ instance (cookie __Host-onboarding) | ก่อน bind owner |
InvitationTokens | token คำเชิญ customer | TTL ตาม FlowDefinition |
CorrectionRequests | คำขอแก้ไขจาก 4-eye | targetSteps |
UserRestrictedProfiles | PII (IdentifierId ฯลฯ) | แยกจาก Users (PDPA) |
Error model
- Validation fail (เช่น field ว่าง) →
FlowEngineExceptionถูก catch →Error.Validation→ 400 พร้อมข้อความที่สื่อความหมาย - exception ที่ไม่ได้ handle →
GlobalExceptionHandlerคืน SYS001 generic ("An unexpected error occurred.") — เป็นมาตรฐาน (500 ไม่โชว์ exception จริง) แต่ stack trace ถูก log ฝั่ง server เสมอ