Files
slp-modular-cms/aidlc-docs/features/master-cms-module/construction/master-backend/nfr-requirements/tech-stack-decisions.md
T

5.4 KiB

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:

services.AddHttpClient<ISlaveApiClient, SlaveApiClient>()
    .AddResilienceHandler("slave-retry", builder =>
    {
        builder.AddRetry(new HttpRetryStrategyOptions
        {
            MaxRetryAttempts = 2,
            Delay = TimeSpan.FromSeconds(1),
            BackoffType = DelayBackoffType.Exponential,
            UseJitter = true,
        });
        builder.AddTimeout(TimeSpan.FromSeconds(options.HttpTimeoutSeconds));
    });

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.