Files
slp-modular-cms/aidlc-docs/features/master-cms-module/construction/slave-availability-extension/nfr-design/logical-components.md
T

9.2 KiB

Logical Components — Unit 2: slave-availability-extension

Component Overview

graph TD
    subgraph Controllers
        MC["MasterController\n(new)"]
    end

    subgraph Services
        IMAS["IMasterAvailabilityService"]
        MAS["MasterAvailabilityService\n(volatile static cache)"]
        IMAKP["IMasterApiKeyProtector"]
        MAKP["MasterApiKeyProtector"]
        MASD["MasterAvailabilityServiceDependencies\n(record)"]
    end

    subgraph Repositories
        IMRR["IMasterRegistrationRepository"]
        MRR["MasterRegistrationRepository"]
    end

    subgraph Data
        AVDBCTX["AvailabilityDbContext\n(new)"]
        MR["MasterRegistration\n(entity)"]
    end

    subgraph Middleware
        AVMW["AvailabilityMiddleware\n(extended)"]
    end

    subgraph External
        DP["IDataProtectionProvider\n(ASP.NET Core)"]
    end

    MC -->|delegates| IMAS
    MAS -.->|implements| IMAS
    MAS -->|uses| MASD
    MASD -->|contains| IMRR
    MASD -->|contains| IMAKP
    MRR -.->|implements| IMRR
    MAKP -.->|implements| IMAKP
    MRR -->|reads/writes| AVDBCTX
    AVDBCTX -->|owns| MR
    MAKP -->|wraps| DP
    AVMW -->|InvokeAsync param| IMAS

    classDef interface fill:#fff,stroke:#63b3ed,stroke-width:2px,color:#2b6cb0
    classDef impl fill:#63b3ed,stroke:#2b6cb0,color:#fff
    classDef entity fill:#FFC107,stroke:#F57F17,color:#000
    classDef infra fill:#9ae6b4,stroke:#2f855a,color:#000
    classDef middleware fill:#CE93D8,stroke:#6A1B9A,color:#000
    classDef external fill:#eee,stroke:#999,color:#333

    class IMAS,IMRR,IMAKP interface
    class MAS,MRR,MAKP,MASD impl
    class MR,AVDBCTX entity
    class MC infra
    class AVMW middleware
    class DP external

Text alternative: MasterController delegates to IMasterAvailabilityService. MasterAvailabilityService uses MasterAvailabilityServiceDependencies which holds IMasterRegistrationRepository and IMasterApiKeyProtector. Repository uses AvailabilityDbContext. MasterApiKeyProtector wraps IDataProtectionProvider. AvailabilityMiddleware receives IMasterAvailabilityService as third InvokeAsync parameter.


Component Specifications

1. MasterRegistration (Entity)

Namespace: SlpModularCms.Modules.Availability.Data.Entities

Property Type Notes
Id Guid PK; always new Guid("00000000-0000-0000-0000-000000000001")
MasterUrl string Required; max 500
ApiKey string Required; max 2000 (encrypted via Data Protection)
RegisteredAt DateTimeOffset Set once on creation
LastContactedAt DateTimeOffset? Updated on every valid master call

2. AvailabilityDbContext

Namespace: SlpModularCms.Modules.Availability.Data

public class AvailabilityDbContext : DbContext
{
    public DbSet<MasterRegistration> MasterRegistrations => Set<MasterRegistration>();
}
Aspect Decision
Migration assembly SlpModularCms.Modules.Availability
Table AvailabilityMasterRegistrations
Applied at AvailabilityModule.UseModuleMigrateAsync()
Connection string ConnectionStrings:DefaultConnection (same as other DbContexts)

3. IMasterRegistrationRepository / MasterRegistrationRepository

Namespace: SlpModularCms.Modules.Availability.Repositories

public interface IMasterRegistrationRepository
{
    Task<MasterRegistration?> GetAsync();
    Task UpsertAsync(MasterRegistration registration);
    Task SaveChangesAsync();
}
Method Notes
GetAsync() Loads singleton by fixed Id; returns null if row does not exist
UpsertAsync(registration) Add if not tracked; Update if tracked or found by Id
SaveChangesAsync() Explicit save; service controls transaction boundary

Registration: services.AddScoped<IMasterRegistrationRepository, MasterRegistrationRepository>()


4. IMasterApiKeyProtector / MasterApiKeyProtector

Namespace: SlpModularCms.Modules.Availability.Services

public interface IMasterApiKeyProtector
{
    string Protect(string plainApiKey);
    string? Unprotect(string encryptedApiKey);  // null on CryptographicException
}

Registration: services.AddSingleton<IMasterApiKeyProtector, MasterApiKeyProtector>()


5. MasterAvailabilityServiceDependencies (Record)

Namespace: SlpModularCms.Modules.Availability.Services

[ExcludeFromCodeCoverage]
public record MasterAvailabilityServiceDependencies(
    IMasterRegistrationRepository Repository,
    IMasterApiKeyProtector KeyProtector,
    ILogger<MasterAvailabilityService> Logger
);

Registration: services.AddScoped<MasterAvailabilityServiceDependencies>()


6. IMasterAvailabilityService / MasterAvailabilityService

Namespace: SlpModularCms.Modules.Availability.Services

public interface IMasterAvailabilityService
{
    Task RegisterAsync(string masterUrl, string apiKey);
    Task PushStatusAsync(string apiKey, bool isAvailable, string? disableMessage);
    Task<string?> GetRegisteredUrlAsync(string apiKey);
    MasterGateStatus GetMasterStatus();
}

Static fields (in implementation):

private static volatile bool _masterIsAvailable = true;
private static volatile string? _masterDisableMessage = null;

Key validation helper (private, reused across all 3 write-path methods):

private async Task<MasterRegistration?> ValidateApiKeyAsync(string apiKey)
{
    var registration = await _deps.Repository.GetAsync();
    if (registration is null) return null;
    var stored = _deps.KeyProtector.Unprotect(registration.ApiKey);
    return stored == apiKey ? registration : null;
}

Registration: services.AddScoped<IMasterAvailabilityService, MasterAvailabilityService>()


7. MasterGateStatus (Record)

Namespace: SlpModularCms.Modules.Availability.Services

[ExcludeFromCodeCoverage]
public record MasterGateStatus(bool IsAvailable, string? DisableMessage);

8. MasterController

Namespace: SlpModularCms.Modules.Availability.Controllers

[ApiController]
[Route("[controller]")]           // → /api/v1/master via ApiPrefixConvention
public class MasterController : ControllerBase
{
    [HttpPost("register")]        // POST /api/v1/master/register
    [HttpPost("status")]          // POST /api/v1/master/status
    [HttpGet("registered-url")]   // GET /api/v1/master/registered-url
}

Constructor: MasterController(IMasterAvailabilityService svc) — single dependency, no record wrapper needed.

Auth: No [Authorize] attribute — API key validated in MasterAvailabilityService.

Response on 401: Unauthorized() — no body to avoid leaking registration state.


9. AvailabilityMiddleware (Extended)

Extended fields (added to existing class):

// Updated bypass prefix list
private static readonly string[] _bypassPrefixes =
[
    "/api/v1/Availability/status",
    "/api/v1/Auth/",
    "/api/v1/Setup/status",
    "/api/v1/master/"              // NEW
];

Updated InvokeAsync signature:

public async Task InvokeAsync(
    HttpContext context,
    IAvailabilityService localSvc,
    IMasterAvailabilityService masterSvc)

Dependency Registration Summary

All new registrations added to AvailabilityModule.RegisterServices:

// Data
services.AddDbContext<AvailabilityDbContext>((sp, options) =>
    options.UseSqlServer(sp.GetRequiredService<IConfiguration>()
        .GetConnectionString("DefaultConnection")));

// Security
services.AddDataProtection();
services.AddSingleton<IMasterApiKeyProtector, MasterApiKeyProtector>();

// Repositories
services.AddScoped<IMasterRegistrationRepository, MasterRegistrationRepository>();

// Services
services.AddScoped<MasterAvailabilityServiceDependencies>();
services.AddScoped<IMasterAvailabilityService, MasterAvailabilityService>();

And in AvailabilityModule.UseModule:

using var scope = app.ApplicationServices.CreateScope();
await scope.ServiceProvider
    .GetRequiredService<AvailabilityDbContext>()
    .Database.MigrateAsync();

New Files Summary

File Project Type
Data/Entities/MasterRegistration.cs Availability Entity
Data/AvailabilityDbContext.cs Availability DbContext
Repositories/IMasterRegistrationRepository.cs Availability Interface
Repositories/MasterRegistrationRepository.cs Availability Implementation
Services/IMasterApiKeyProtector.cs Availability Interface
Services/MasterApiKeyProtector.cs Availability Implementation
Services/MasterGateStatus.cs Availability Record
Services/MasterAvailabilityServiceDependencies.cs Availability Record
Services/IMasterAvailabilityService.cs Availability Interface
Services/MasterAvailabilityService.cs Availability Implementation
Controllers/MasterController.cs Availability Controller
Middleware/AvailabilityMiddleware.cs Availability Modified (extended)
AvailabilityModule.cs Availability Modified (registration + migration)
Data/Migrations/* Availability EF Core auto-generated