Tech Stack Decisions — Unit 1: master-backend
HTTP Client & Resilience
Decision: Typed HTTP Client via IHttpClientFactory + Polly
| Aspect |
Decision |
Rationale |
| HTTP client abstraction |
ISlaveApiClient / SlaveApiClient typed client |
Testable via mock injection; clean contract boundary |
| Client registration |
services.AddHttpClient<ISlaveApiClient, SlaveApiClient>() |
Framework manages HttpClient lifetime and connection pooling |
| Retry policy |
Polly ResiliencePipelineBuilder with AddRetry |
Industry standard .NET resilience library; integrates natively with IHttpClientFactory via AddResilienceHandler |
| Retry configuration |
3 total attempts; delays: 1s → 2s (exponential); jitter optional |
Bounded worst-case latency; exponential reduces thundering herd on widespread slave outages |
| Per-attempt timeout |
MasterModuleOptions.HttpTimeoutSeconds (default 10s) |
Configurable per-environment; keeps controller responses bounded |
NuGet package required: Microsoft.Extensions.Http.Resilience (includes Polly integration)
Registration pattern:
Data Protection
Decision: Default ASP.NET Core Data Protection (file system)
| Aspect |
Decision |
Rationale |
| Key storage |
Default file system (no explicit PersistKeysTo* call) |
Zero configuration; acceptable for single-instance; owner manages production key persistence |
| Purpose string |
"SlpModularCms.Master.ApiKey" |
Scoped protection; prevents cross-purpose decryption |
| Registration |
services.AddDataProtection() (already called by framework if not explicitly called) |
No extra setup needed beyond injecting IDataProtectionProvider |
Production note (to be included in Unit 4 README update): For containerized or multi-instance deployments, configure a persistent key ring (e.g., PersistKeysToDbContext, PersistKeysToAzureBlobStorage). Without it, restarting the container causes all encrypted ApiKey values to become unreadable.
Background Service
Decision: .NET BackgroundService + PeriodicTimer
| Aspect |
Decision |
Rationale |
| Base class |
BackgroundService |
Built-in .NET hosted service; lifecycle managed by IHostApplicationLifetime |
| Timer mechanism |
PeriodicTimer |
Allocates less than Timer; await-friendly; cancellation-aware |
| Scope management |
IServiceScopeFactory.CreateScope() per tick |
Required because ICmsInstanceService is Scoped; prevents captive dependency |
| Exception handling |
try/catch around entire tick body; log Error and continue |
Prevents background service crash on unexpected errors |
Logging
Decision: ILogger<T> structured logging
| Scenario |
Log Level |
Structured Fields |
| Status push failed (slave unreachable) |
Error |
instanceId, slaveUrl, exception |
| Integrity check: slave unreachable |
Warning |
instanceId, slaveUrl |
| Integrity check: URL mismatch detected |
Warning |
instanceId, slaveUrl, expectedUrl, registeredUrl |
| Integrity check: re-registration succeeded |
Information |
instanceId, slaveUrl |
| Integrity check: re-registration failed |
Warning |
instanceId, slaveUrl |
| Background service tick started |
Debug |
instanceCount |
| ApiKey decryption failed |
Error |
instanceId (do NOT log the key itself) |
EF Core / Database
Decision: Per-module MasterDbContext with own migrations
| Aspect |
Decision |
| DbContext class |
MasterDbContext : DbContext in SlpModularCms.Modules.Master |
| Migration assembly |
SlpModularCms.Modules.Master (same project) |
| Migration application |
app.ApplicationServices.CreateScope() → MasterDbContext.Database.MigrateAsync() in MasterModule.UseModule(IApplicationBuilder) |
| Tables owned |
CmsInstances, DataProtectionKeys (if needed in future) |
| Connection string |
Reuses the same connection string as ApplicationDbContext (from ConnectionStrings:DefaultConnection) |
Test Framework
Decision: xUnit + Moq (matching existing test projects)
| Aspect |
Decision |
Rationale |
| Test framework |
xUnit |
Matches existing Availability.Tests project |
| Mocking |
Moq |
Matches existing test projects |
| Coverage tool |
coverlet (via .runsettings or dotnet test --collect) |
Already in use in existing test projects |
[ExcludeFromCodeCoverage] targets |
MasterModule, EF Core migration files, DTO records |
Q4 decision |
| HTTP testing |
Mock ISlaveApiClient via Moq |
Typed client interface enables clean mocking without HttpMessageHandler fakes |
Summary of New Dependencies
| Package |
Version |
Purpose |
Microsoft.Extensions.Http.Resilience |
Latest stable |
Polly integration for IHttpClientFactory retry policies |
All other dependencies (EF Core, ASP.NET Core, xUnit, Moq) are already present in the solution.