Layer 5: MyApp.Api -- The Production Host
This is where the chain becomes a deployable application. The API project references MyApp.Domain (and transitively, everything else). It wires the DI container, exposes controllers, and optionally enables compliance validation.
DI Registration
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Wire the requirement chain
builder.Services.AddScoped<IUserRolesSpec, AuthorizationService>();
builder.Services.AddScoped<IJwtRefreshSpec, JwtRefreshService>();
// Optional: enable compliance mode for audit-sensitive operations
builder.Services.AddScoped<UserRolesValidator>(sp =>
new UserRolesValidator(sp.GetRequiredService<IUserRolesSpec>()));
// Optional: enable requirement compliance checking at startup
builder.Services.AddHostedService<RequirementComplianceCheck>();
var app = builder.Build();
app.MapControllers();
app.Run();// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Wire the requirement chain
builder.Services.AddScoped<IUserRolesSpec, AuthorizationService>();
builder.Services.AddScoped<IJwtRefreshSpec, JwtRefreshService>();
// Optional: enable compliance mode for audit-sensitive operations
builder.Services.AddScoped<UserRolesValidator>(sp =>
new UserRolesValidator(sp.GetRequiredService<IUserRolesSpec>()));
// Optional: enable requirement compliance checking at startup
builder.Services.AddHostedService<RequirementComplianceCheck>();
var app = builder.Build();
app.MapControllers();
app.Run();Controller -- Normal Production Flow
namespace MyApp.Api.Controllers;
using MyApp.Specifications;
using MyApp.SharedKernel;
using MyApp.Requirements.Features;
[ApiController]
[Route("api/users")]
[ForRequirement(typeof(UserRolesFeature))]
public class UsersController : ControllerBase
{
private readonly IUserRolesSpec _spec;
public UsersController(IUserRolesSpec spec) => _spec = spec;
[HttpPost("{targetId}/role")]
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public IActionResult AssignRole(Guid targetId, [FromBody] AssignRoleRequest request)
{
var actingUser = HttpContext.GetCurrentUser();
var targetUser = _users.GetById(new UserId(targetId));
var role = _roles.GetById(new RoleId(request.RoleId));
var result = _spec.AssignRole(actingUser, targetUser, role);
return result.IsSuccess
? Ok(new { message = "Role assigned" })
: BadRequest(new { error = result.Reason });
}
}namespace MyApp.Api.Controllers;
using MyApp.Specifications;
using MyApp.SharedKernel;
using MyApp.Requirements.Features;
[ApiController]
[Route("api/users")]
[ForRequirement(typeof(UserRolesFeature))]
public class UsersController : ControllerBase
{
private readonly IUserRolesSpec _spec;
public UsersController(IUserRolesSpec spec) => _spec = spec;
[HttpPost("{targetId}/role")]
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public IActionResult AssignRole(Guid targetId, [FromBody] AssignRoleRequest request)
{
var actingUser = HttpContext.GetCurrentUser();
var targetUser = _users.GetById(new UserId(targetId));
var role = _roles.GetById(new RoleId(request.RoleId));
var result = _spec.AssignRole(actingUser, targetUser, role);
return result.IsSuccess
? Ok(new { message = "Role assigned" })
: BadRequest(new { error = result.Reason });
}
}The [ForRequirement] on the controller and action method means the generated OpenAPI schema includes requirement metadata:
{
"paths": {
"/api/users/{targetId}/role": {
"post": {
"x-requirement": "UserRolesFeature",
"x-acceptance-criterion": "AdminCanAssignRoles",
"summary": "Assign a role to a user"
}
}
}
}{
"paths": {
"/api/users/{targetId}/role": {
"post": {
"x-requirement": "UserRolesFeature",
"x-acceptance-criterion": "AdminCanAssignRoles",
"summary": "Assign a role to a user"
}
}
}
}Startup Compliance Check
public class RequirementComplianceCheck : IHostedService
{
private readonly ILogger<RequirementComplianceCheck> _logger;
public Task StartAsync(CancellationToken ct)
{
// TraceabilityMatrix is source-generated
foreach (var (type, entry) in TraceabilityMatrix.Entries)
{
var info = RequirementRegistry.All[type];
var totalACs = info.AcceptanceCriteria.Length;
var testedACs = entry.AcceptanceCriteriaCoverage.Count;
if (testedACs < totalACs)
_logger.LogWarning("{Feature}: {Tested}/{Total} ACs tested ({Missing} missing)",
info.Title, testedACs, totalACs,
string.Join(", ", info.AcceptanceCriteria
.Except(entry.AcceptanceCriteriaCoverage.Keys)));
else
_logger.LogInformation("{Feature}: all {Total} ACs covered",
info.Title, totalACs);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}public class RequirementComplianceCheck : IHostedService
{
private readonly ILogger<RequirementComplianceCheck> _logger;
public Task StartAsync(CancellationToken ct)
{
// TraceabilityMatrix is source-generated
foreach (var (type, entry) in TraceabilityMatrix.Entries)
{
var info = RequirementRegistry.All[type];
var totalACs = info.AcceptanceCriteria.Length;
var testedACs = entry.AcceptanceCriteriaCoverage.Count;
if (testedACs < totalACs)
_logger.LogWarning("{Feature}: {Tested}/{Total} ACs tested ({Missing} missing)",
info.Title, testedACs, totalACs,
string.Join(", ", info.AcceptanceCriteria
.Except(entry.AcceptanceCriteriaCoverage.Keys)));
else
_logger.LogInformation("{Feature}: all {Total} ACs covered",
info.Title, totalACs);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}DLL Artifacts at Build Time
After dotnet build, the solution produces:
bin/
├── MyApp.Requirements.dll ← Feature types, AC methods, generated attributes
├── MyApp.SharedKernel.dll ← User, Role, Permission, Result
├── MyApp.Specifications.dll ← IUserRolesSpec, IJwtRefreshSpec, validator bridges
├── MyApp.Domain.dll ← AuthorizationService : IUserRolesSpec
├── MyApp.Api.dll ← Controllers, DI wiring, startup compliance
└── MyApp.Tests.dll ← Type-linked tests (not deployed)
Generated at compile time (embedded in respective DLLs):
├── RequirementRegistry.g.cs ← Type catalog + hierarchy
├── TraceabilityMatrix.g.cs ← Requirement → Implementation → Test mapping
└── Compiler diagnostics ← IDE warnings for untested ACsbin/
├── MyApp.Requirements.dll ← Feature types, AC methods, generated attributes
├── MyApp.SharedKernel.dll ← User, Role, Permission, Result
├── MyApp.Specifications.dll ← IUserRolesSpec, IJwtRefreshSpec, validator bridges
├── MyApp.Domain.dll ← AuthorizationService : IUserRolesSpec
├── MyApp.Api.dll ← Controllers, DI wiring, startup compliance
└── MyApp.Tests.dll ← Type-linked tests (not deployed)
Generated at compile time (embedded in respective DLLs):
├── RequirementRegistry.g.cs ← Type catalog + hierarchy
├── TraceabilityMatrix.g.cs ← Requirement → Implementation → Test mapping
└── Compiler diagnostics ← IDE warnings for untested ACs