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<MasterRegistration> MasterRegistrations |
| Table name |
AvailabilityMasterRegistrations |
| Connection string |
Reuses ConnectionStrings:DefaultConnection (same as ApplicationDbContext and MasterDbContext) |
| Registration |
services.AddDbContext<AvailabilityDbContext>((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<MasterRegistration?> |
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.