Private Docs

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 (ชั้นในไม่รู้จักชั้นนอก):

01 · APIControllers · Middleware
03 · ApplicationCommands · FlowEngine · Handlers
02 · InfrastructureRepositories · EF Core
PostgreSQLฐานข้อมูล

04 · Domain (Entities · Ports/interfaces) เป็นแกนใน — Application & Infrastructure อ้างเข้าหา ไม่ใช่ทางกลับ

ชั้นหน้าที่ตัวอย่างใน Lego
01.APIรับ HTTP, auth, แปลง Result → HTTPOnboardingV2Controller, GlobalExceptionHandler
03.Applicationbusiness orchestrationExecuteStepActionHandler, FlowEngine, step handlers
02.Infrastructurepersistence จริง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 เรียงชั้น (จากนอกสุดเข้าใน):

Performance Encryption Audit Validation Logging Transaction Handler
  • 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”:

  1. deserialize Snapshot จาก instance.FlowSnapshotJson → หา step ปัจจุบัน
  2. Back → ถอยไป interactive step ก่อนหน้า
  3. resolve Handler จาก stepTypehandler.ExecuteAsync(ctx)
  4. ถ้า !result.Successthrow FlowEngineException(errorCode, message)
  5. UpsertAsync(FlowInstanceStepData) เก็บ output ของ step
  6. Next → เลื่อนไป interactive step ถัดไป แล้ว AdvanceAutomaticAsync (รัน Automatic step ที่คั่นจน finalize หรือเจอ interactive)
  7. 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 ประกอบ:

ตารางเก็บอะไรหมายเหตุ
FlowDefinitionstemplate flow (admin config)+ FlowSteps ลำดับ step
StepCatalogทะเบียน step type ที่ใช้ได้ExecutionMode, SkipScope, DependenciesJson
FlowInstancesการรัน 1 ครั้งStatus, CurrentStepIndex, FlowSnapshotJson, OwnerUserId
FlowInstanceStepDataoutput ของแต่ละ stepคอลัมน์ DataJson เป็น jsonb
EditSessionsผูก anonymous browser ↔ instance (cookie __Host-onboarding)ก่อน bind owner
InvitationTokenstoken คำเชิญ customerTTL ตาม FlowDefinition
CorrectionRequestsคำขอแก้ไขจาก 4-eyetargetSteps
UserRestrictedProfilesPII (IdentifierId ฯลฯ)แยกจาก Users (PDPA)

Error model

Fail()validation ไม่ผ่าน
FlowEngineException → catch → Result.Failure
400ApiMatch + ข้อความไทยที่สื่อ
exception อื่นไม่ได้ handle
GlobalExceptionHandler
500 · SYS001generic (log จริงฝั่ง server)
  • Validation fail (เช่น field ว่าง) → FlowEngineException ถูก catch → Error.Validation → 400 พร้อมข้อความที่สื่อความหมาย
  • exception ที่ไม่ได้ handleGlobalExceptionHandler คืน SYS001 generic ("An unexpected error occurred.") — เป็นมาตรฐาน (500 ไม่โชว์ exception จริง) แต่ stack trace ถูก log ฝั่ง server เสมอ