7.0 KiB
7.0 KiB
Business Rules — Unit 1: master-backend
BR-01 — Status Update Decision Logic
graph TD
Start(["UpdateStatusAsync called"])
CheckExists{"Entity exists\nfor given id?"}
NotFound["Throw NotFoundException\n404 to caller"]
CheckMsg{"newStatus = NotAvailable\nAND disableMessage\nis null or empty?"}
ValidationErr["Throw ValidationException\nDisableMessage required"]
CheckInactive{"newStatus\n= Inactive?"}
SetInactive["Status = Inactive\nDisableMessage = null\nNo HTTP push\nSlaveContactSuccess = true"]
PersistStatus["Persist Status + DisableMessage\nto MasterDbContext"]
DecryptKey["Decrypt ApiKey\nvia IDataProtector"]
PushSlave["PushStatusAsync\nto slave endpoint"]
PushOk{"HTTP push\nsucceeded?"}
UpdatePushed["LastStatusPushedAt = UtcNow\nSave"]
ReturnOk["Return UpdateStatusResult\nSuccess=true\nSlaveContactSuccess=true"]
ReturnWarn["Return UpdateStatusResult\nSuccess=true\nSlaveContactSuccess=false"]
Done(["Return result to controller"])
Start --> CheckExists
CheckExists -->|"no"| NotFound
CheckExists -->|"yes"| CheckMsg
CheckMsg -->|"yes — invalid"| ValidationErr
CheckMsg -->|"no — valid"| CheckInactive
CheckInactive -->|"yes"| SetInactive --> Done
CheckInactive -->|"no"| PersistStatus --> DecryptKey --> PushSlave --> PushOk
PushOk -->|"yes"| UpdatePushed --> ReturnOk --> Done
PushOk -->|"no"| ReturnWarn --> Done
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 error fill:#FC8181,stroke:#C53030,stroke-width:2px,color:#000
class CheckExists,CheckMsg,CheckInactive,PushOk decision
class PersistStatus,DecryptKey,PushSlave,UpdatePushed,SetInactive action
class Start,Done terminal
class NotFound,ValidationErr error
Text alternative: Load entity (404 if missing) → validate DisableMessage required for NotAvailable → for Inactive skip push → for others persist, decrypt key, push to slave, set SlaveContactSuccess based on push result.
BR-02 — Integrity Check Decision Logic
graph TD
Start(["VerifyIntegrityAsync\nper instance"])
GetUrl["GetRegisteredMasterUrlAsync\n(slaveUrl, plainApiKey)"]
Reachable{"Slave\nreachable?"}
SetFailed["LastIntegrityCheckFailedAt = UtcNow\nSave — continue to next"]
UrlMatch{"registeredMasterUrl\n= expected masterUrl?"}
ClearOk["LastIntegrityCheckFailedAt = null\nLastContactedAt = UtcNow\nSave"]
ReRegister["RegisterMasterAsync\n(slaveUrl, plainApiKey, masterUrl)"]
RegOk{"Re-registration\nsucceeded?"}
ClearAfterReg["LastIntegrityCheckFailedAt = null\nLastContactedAt = UtcNow\nSave"]
SetFailedReg["LastIntegrityCheckFailedAt = UtcNow\nSave"]
Next(["Next instance"])
Start --> GetUrl --> Reachable
Reachable -->|"no"| SetFailed --> Next
Reachable -->|"yes"| UrlMatch
UrlMatch -->|"match"| ClearOk --> Next
UrlMatch -->|"mismatch"| ReRegister --> RegOk
RegOk -->|"yes"| ClearAfterReg --> Next
RegOk -->|"no"| SetFailedReg --> Next
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 error fill:#FC8181,stroke:#C53030,stroke-width:2px,color:#000
class Reachable,UrlMatch,RegOk decision
class GetUrl,SetFailed,ClearOk,ReRegister,ClearAfterReg,SetFailedReg action
class Start,Next terminal
Text alternative: For each active slave — attempt to get its registered master URL; if unreachable set failure flag; if reachable and URL matches clear flag; if mismatch re-register; clear flag on success, set flag on failure.
Validation Rules
| Rule | Field | Condition | Error |
|---|---|---|---|
| BR-VAL-01 | Name |
Must not be null or whitespace | Name is required |
| BR-VAL-02 | Url |
Must not be null or whitespace | Url is required |
| BR-VAL-03 | Url |
Must start with http:// or https:// |
Url must be a valid absolute HTTP(S) URL |
| BR-VAL-04 | ApiKey |
Must not be null or whitespace (on create) | ApiKey is required |
| BR-VAL-05 | DisableMessage |
Required (non-empty) when Status = NotAvailable |
DisableMessage is required when status is NotAvailable |
| BR-VAL-06 | Status |
Must be a valid CmsInstanceStatus enum value |
Invalid status value |
| BR-VAL-07 | id (update) |
CmsInstance with given id must exist | CmsInstance not found (404) |
Status Transition Rules
| From | To | DisableMessage | HTTP Push | Notes |
|---|---|---|---|---|
| Any | Available |
Clear to null | Yes | Slave re-enabled |
| Any | NotAvailable |
Required, non-empty | Yes | Slave disabled with message |
| Any | Inactive |
Clear to null | No | Master stops all contact |
Inactive |
Available |
Clear to null | Yes | Reactivation |
Inactive |
NotAvailable |
Required, non-empty | Yes | Reactivation with disable |
ApiKey Encryption Rules
| Rule | Description |
|---|---|
| BR-ENC-01 | ApiKey is encrypted via IDataProtector before writing to MasterDbContext |
| BR-ENC-02 | ApiKey is decrypted via IDataProtector immediately before each HTTP call requiring it |
| BR-ENC-03 | ApiKey is never included in CmsInstanceDto or any other API response |
| BR-ENC-04 | ApiKey is accepted in CreateCmsInstanceRequest on creation only; no update endpoint for ApiKey |
Master URL Resolution Rules
| Context | Resolution Strategy |
|---|---|
| Controller-originated calls (Add) | Derive from HttpContext.Request scheme + host + (optional port) via IHttpContextAccessor |
| Background service calls (Integrity Check) | Read MasterModuleOptions.MasterUrl from configuration |
MasterModuleOptions.MasterUrl is null in background context |
Log a warning; skip registration/integrity for that cycle |
HTTP Contact Exclusion Rules
| Rule | Description |
|---|---|
| BR-CONTACT-01 | Instances with Status = Inactive are excluded from GetActiveAsync and never contacted via HTTP |
| BR-CONTACT-02 | Status push is skipped when transitioning any status → Inactive |
| BR-CONTACT-03 | Integrity check runs only against instances where Status != Inactive |
Background Service Rules
| Rule | Description |
|---|---|
| BR-BG-01 | IntegrityCheckBackgroundService resolves ICmsInstanceService via IServiceScopeFactory per tick (not injected directly, as service is Scoped) |
| BR-BG-02 | Each tick creates and disposes its own IServiceScope |
| BR-BG-03 | Exceptions within a single slave's integrity check are caught, logged, and do not abort processing for remaining slaves |
| BR-BG-04 | If MasterModuleOptions.MasterUrl is null or empty, the background service logs a warning and skips the entire integrity check for that cycle |