# Tech Stack Decisions — Unit 2: slave-availability-extension ## Data Protection ### Decision: `IMasterApiKeyProtector` wrapping ASP.NET Core Data Protection | Aspect | Decision | Rationale | |--------|----------|-----------| | Interface | `IMasterApiKeyProtector` with `Protect(string)` / `Unprotect(string)` | Testable; mirrors Unit 1's `IApiKeyProtector` pattern | | Implementation | `MasterApiKeyProtector : IMasterApiKeyProtector` | Wraps `IDataProtectionProvider`; purpose-scoped | | Purpose string | `"SlpModularCms.Availability.MasterApiKey"` | Prevents cross-module decryption with Unit 1's scope | | On `Unprotect` failure | Catch `CryptographicException`, return `null` | Service treats null as key mismatch → 401; logs Error | | Key ring | Default file system (inherits app-level `AddDataProtection()` setup) | No extra configuration needed; same caveat as Unit 1 regarding containerized deployments | **Production note**: Same as Unit 1 — for multi-instance or containerized deployments, configure a shared key ring (`PersistKeysToDbContext`, `PersistKeysToAzureBlobStorage`, etc.). Without it, a restarted container cannot decrypt keys stored by the previous instance. --- ## Static Cache ### Decision: `volatile` static fields in `MasterAvailabilityService` | Aspect | Decision | Rationale | |--------|----------|-----------| | `_masterIsAvailable` | `private static volatile bool` | Atomic read/write for bool; `volatile` ensures CPU cache flush visibility | | `_masterDisableMessage` | `private static volatile string?` | Reference assignment is atomic in .NET; `volatile` ensures visibility | | Default | `_masterIsAvailable = true`, `_masterDisableMessage = null` | Fail-open: process startup = Available | | Write location | `MasterAvailabilityService.PushStatusAsync` only | Single write point; no other code modifies cache | | Read location | `AvailabilityMiddleware.InvokeAsync` only | Single read point; no async overhead | **Why not `lock`**: No multi-field invariant to protect (fields are read/written independently). `volatile` matches the existing pattern in `PersistentAvailabilityService` (`_lastErrorTime`). --- ## EF Core / Database ### Decision: New `AvailabilityDbContext` with own migrations | Aspect | Decision | |--------|----------| | DbContext class | `AvailabilityDbContext : DbContext` in `SlpModularCms.Modules.Availability` | | Migration assembly | `SlpModularCms.Modules.Availability` (same project) | | Migration application | `app.ApplicationServices.CreateScope()` → `AvailabilityDbContext.Database.MigrateAsync()` in `AvailabilityModule.UseModule(IApplicationBuilder)` | | DbSet | `DbSet MasterRegistrations` | | Table name | `AvailabilityMasterRegistrations` | | Connection string | Reuses `ConnectionStrings:DefaultConnection` (same as `ApplicationDbContext` and `MasterDbContext`) | | Registration | `services.AddDbContext((sp, options) => ...)` using `IConfiguration` from service provider | **Singleton enforcement**: `MasterRegistration.Id` is always `new Guid("00000000-0000-0000-0000-000000000001")`. EF `AddOrUpdate` via `ExecuteUpdateAsync` / find-by-id pattern. --- ## Repository ### Decision: `IMasterRegistrationRepository` / `MasterRegistrationRepository` | Method | Signature | Notes | |--------|-----------|-------| | `GetAsync` | `Task` | Loads singleton by fixed Id; returns null if not exists | | `UpsertAsync` | `Task UpsertAsync(MasterRegistration registration)` | Add or Update based on whether row exists | | `SaveChangesAsync` | `Task SaveChangesAsync()` | Explicit save; keeps service in control of transaction boundary | --- ## Controller ### Decision: New `MasterController` in Availability module | Aspect | Decision | |--------|----------| | Class | `MasterController : ControllerBase` in `SlpModularCms.Modules.Availability.Controllers` | | Route | `[Route("[controller]")]` → `/api/v1/master` via `ApiPrefixConvention("api/v1")` | | Auth | No `[Authorize]` — API key validated in service layer | | Response on 401 | `Unauthorized()` (HTTP 401) — no `ProblemDetails` body to avoid leaking info | | Response on success | `Ok()` for register/status; `Ok(new { MasterUrl })` for registered-url | --- ## New Dependencies | Package | Already present? | Notes | |---------|-----------------|-------| | `Microsoft.AspNetCore.DataProtection` | Yes (shared framework) | No NuGet addition needed | | EF Core SqlServer | Yes (via Core project) | No addition needed | | xUnit / NSubstitute / FluentAssertions | Yes (existing test projects) | Reference same versions as `Availability.Tests` | | EF Core InMemory | Likely yes | Confirm in `Availability.Tests.csproj` | **Net new NuGet packages required**: None.