Files
slp-modular-cms/aidlc-docs/features/master-cms-module/inception/application-design/services.md
T

9.5 KiB

Services — Master CMS Module

Service Definitions

Master-Side Services

CmsInstanceService

  • Interface: ICmsInstanceService
  • Lifetime: Scoped
  • Injected Dependencies: ICmsInstanceRepository, ISlaveApiClient, IOptions<MasterModuleOptions>, ILogger<CmsInstanceService>
  • Responsibilities: Business orchestration — coordinates repository (data) and SlaveApiClient (HTTP side-effects); enforces business rules (mandatory DisableMessage, Inactive cannot be pushed)
  • Key orchestration: On AddAsync → persist entity then call RegisterMasterAsync; on UpdateStatusAsync → validate, persist, then call PushStatusAsync; on VerifyIntegrityAsync → query all active instances, call GetRegisteredMasterUrlAsync per instance, re-register on mismatch

SlaveApiClient

  • Interface: ISlaveApiClient
  • Lifetime: Transient (managed by IHttpClientFactory)
  • Registration: services.AddHttpClient<ISlaveApiClient, SlaveApiClient>()
  • Injected Dependencies: HttpClient (injected by framework)
  • Responsibilities: Typed HTTP client for all Master → Slave API calls; sets X-Master-Api-Key header per call; deserializes responses; returns success flags rather than throwing (callers decide error handling)
  • Endpoints called:
    • POST {slaveUrl}/api/internal/master/register
    • PUT {slaveUrl}/api/v1/Availability/admin/status (reuses existing slave endpoint)
    • GET {slaveUrl}/api/internal/master/registration (integrity check)

IntegrityCheckBackgroundService

  • Lifetime: Singleton (registered via services.AddHostedService<IntegrityCheckBackgroundService>())
  • Injected Dependencies: IServiceScopeFactory, IOptions<MasterModuleOptions>, ILogger<IntegrityCheckBackgroundService>
  • Responsibilities: Periodic background loop; resolves ICmsInstanceService via IServiceScopeFactory per tick (required because ICmsInstanceService is Scoped); runs VerifyIntegrityAsync(); interval from MasterModuleOptions.IntegrityCheckIntervalMinutes

Slave-Side Services

MasterAvailabilityService

  • Interface: IMasterAvailabilityService
  • Lifetime: Scoped
  • Injected Dependencies: AvailabilityDbContext, IHttpClientFactory, IOptions<MasterModuleOptions>, ILogger<MasterAvailabilityService>
  • Responsibilities: Checks DB for MasterRegistration; if none exists returns HasMaster = false; if exists checks cache freshness against MasterModuleOptions.CacheMinutes; pulls from Master via HTTP on cache miss; returns cached value on HTTP failure (fail-open, NFR-MASTER-01)
  • Cache fields (static): _cachedStatus (default Available), _cachedDisableMessage, _lastFetchedAt, _cachedMasterUrl
  • Exemption: No special logic needed — a Master CMS instance never calls RegisterMaster on itself, so MasterRegistrations table is empty → HasMaster = false always on a Master instance (FR-MASTER-09)

Orchestration Flows

Flow 1 — Add Slave CMS

sequenceDiagram
    box rgba(99,179,237,0.3) Frontend
        participant UI as Browser
    end
    box rgba(154,230,180,0.3) Master CMS
        participant Ctrl as CmsInstanceController
        participant Svc as CmsInstanceService
        participant Repo as CmsInstanceRepository
        participant Client as SlaveApiClient
    end
    box rgba(246,224,94,0.3) Slave CMS
        participant SlaveEndpoint as AvailabilityController
    end

    UI->>Ctrl: POST /api/v1/CmsInstances
    Ctrl->>Svc: AddAsync(request)
    Svc->>Repo: AddAsync(entity)
    Repo-->>Svc: entity tracked
    Svc->>Repo: SaveChangesAsync()
    Svc->>Client: RegisterMasterAsync(slaveUrl, apiKey, masterUrl)
    Client->>SlaveEndpoint: POST /api/internal/master/register
    SlaveEndpoint-->>Client: 200 OK
    Client-->>Svc: true
    Svc->>Repo: UpdateAsync (LastContactedAt)
    Repo-->>Svc: updated
    Svc->>Repo: SaveChangesAsync()
    Svc-->>Ctrl: CmsInstanceDto
    Ctrl-->>UI: 201 Created

Text alternative: Browser posts new slave to master controller → service persists → calls slave registration endpoint → updates LastContactedAt → returns DTO.


Flow 2 — Set Slave Status

sequenceDiagram
    box rgba(99,179,237,0.3) Frontend
        participant UI as Browser
    end
    box rgba(154,230,180,0.3) Master CMS
        participant Ctrl as CmsInstanceController
        participant Svc as CmsInstanceService
        participant Repo as CmsInstanceRepository
        participant Client as SlaveApiClient
    end
    box rgba(246,224,94,0.3) Slave CMS
        participant SlaveCtrl as AvailabilityController
    end

    UI->>Ctrl: PUT /api/v1/CmsInstances/{id}/status
    Ctrl->>Svc: UpdateStatusAsync(id, status, disableMessage)
    Svc->>Repo: GetByIdAsync(id)
    Repo-->>Svc: CmsInstance
    Note over Svc: Validate DisableMessage required if NotAvailable
    Svc->>Repo: UpdateAsync (Status, DisableMessage)
    Svc->>Repo: SaveChangesAsync()
    Svc->>Client: PushStatusAsync(url, apiKey, status, disableMessage)
    Client->>SlaveCtrl: PUT /api/v1/Availability/admin/status
    SlaveCtrl-->>Client: 200 OK
    Client-->>Svc: true
    Svc->>Repo: UpdateAsync (LastStatusPushedAt)
    Svc->>Repo: SaveChangesAsync()
    Svc-->>Ctrl: void
    Ctrl-->>UI: 200 OK

Text alternative: Browser sends status update → master validates, persists, pushes to slave endpoint → updates LastStatusPushedAt → returns 200.


Flow 3 — Integrity Check (Background)

sequenceDiagram
    box rgba(200,200,200,0.3) Background
        participant Timer as PeriodicTimer
        participant BgSvc as IntegrityCheckBackgroundService
    end
    box rgba(154,230,180,0.3) Master CMS
        participant Svc as CmsInstanceService
        participant Repo as CmsInstanceRepository
        participant Client as SlaveApiClient
    end
    box rgba(246,224,94,0.3) Slave CMS
        participant SlaveEndpoint as Slave API
    end

    Timer->>BgSvc: Tick (every IntegrityCheckIntervalMinutes)
    BgSvc->>Svc: VerifyIntegrityAsync()
    Svc->>Repo: GetActiveAsync()
    Repo-->>Svc: list of active CmsInstances
    loop for each active instance
        Svc->>Client: GetRegisteredMasterUrlAsync(slaveUrl, apiKey)
        Client->>SlaveEndpoint: GET /api/internal/master/registration
        SlaveEndpoint-->>Client: registeredMasterUrl
        Client-->>Svc: registeredMasterUrl
        alt URL mismatch
            Svc->>Client: RegisterMasterAsync(slaveUrl, apiKey, masterUrl)
            Client->>SlaveEndpoint: POST /api/internal/master/register
            SlaveEndpoint-->>Client: 200 OK
            Client-->>Svc: true
        end
    end
    Svc-->>BgSvc: done

Text alternative: Background timer triggers integrity service → per active slave checks registered URL → re-registers if mismatch.


Flow 4 — Two-Phase Availability Gate (Slave Middleware)

flowchart TD
    Start(["Incoming HTTP Request"])
    Bypass{"Bypass prefix?"}
    HasMaster{"MasterRegistration\nexists in DB?"}
    CacheFresh{"Cache fresh?"}
    PullMaster["Pull status from Master\n(HTTP GET)"]
    PullOk{"Pull successful?"}
    MasterStatus{"Master status?"}
    LocalCheck["Existing local gate\nIAvailabilityService.IsAvailableAsync()"]
    AdminBypass{"Admin bypass\n(Owner/Admin JWT)?"}
    Block503Master["503 Service Unavailable\n+ DisableMessage"]
    Block503Local["503 / Maintenance"]
    Pass(["Pass request to next middleware"])

    Start --> Bypass
    Bypass -->|yes| Pass
    Bypass -->|no| HasMaster
    HasMaster -->|no| LocalCheck
    HasMaster -->|yes| CacheFresh
    CacheFresh -->|yes| MasterStatus
    CacheFresh -->|no| PullMaster
    PullMaster --> PullOk
    PullOk -->|yes| MasterStatus
    PullOk -->|no - use cached| MasterStatus
    MasterStatus -->|NotAvailable| Block503Master
    MasterStatus -->|Available| LocalCheck
    LocalCheck -->|Available| Pass
    LocalCheck -->|NotAvailable or Maintenance| AdminBypass
    AdminBypass -->|yes| Pass
    AdminBypass -->|no| Block503Local

    classDef decision fill:#FFC107,stroke:#F57F17,stroke-width:2px,color:#000
    classDef action fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000
    classDef terminal fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000
    classDef block fill:#FC8181,stroke:#C53030,stroke-width:2px,color:#000
    class Bypass,HasMaster,CacheFresh,PullOk,MasterStatus,AdminBypass decision
    class PullMaster,LocalCheck action
    class Start terminal
    class Pass terminal
    class Block503Master,Block503Local block

Text alternative: Request enters middleware → check bypass → if master registered check cached/pulled status → if NotAvailable return 503 with DisableMessage → else proceed to existing local availability gate.


Flow 5 — Slave Registration (Master Registers Itself)

sequenceDiagram
    box rgba(154,230,180,0.3) Master CMS
        participant Client as SlaveApiClient
    end
    box rgba(246,224,94,0.3) Slave CMS
        participant Ctrl as AvailabilityController
        participant DB as AvailabilityDbContext
    end

    Client->>Ctrl: POST /api/internal/master/register\nHeader: X-Master-Api-Key\nBody: {masterUrl}
    Ctrl->>Ctrl: Validate API key vs MasterModuleOptions.ApiKey
    alt Invalid key
        Ctrl-->>Client: 401 Unauthorized
    else Valid key
        Ctrl->>DB: Upsert MasterRegistration (masterUrl)
        DB-->>Ctrl: saved
        Ctrl-->>Client: 200 OK
    end

Text alternative: Master posts registration with API key header → slave validates key → upserts MasterRegistration record → returns 200.