Layer 3: MyApp.Implementation -- Compiler-Enforced Domain Code
This project references MyApp.Specifications (and transitively, MyApp.Requirements). The domain implements spec interfaces. The colon operator IS the guarantee. Every class and method is decorated with [ForRequirement] for full IDE navigability.
namespace MyApp.Domain.Auth;
using MyApp.Specifications;
using MyApp.Requirements.Features;
/// <summary>
/// Domain service implementing user roles.
/// The compiler FORCES this class to implement all three IUserRolesSpec methods.
/// If a new AC is added to the specification interface, this class
/// will not compile until the new method is implemented.
/// </summary>
[ForRequirement(typeof(UserRolesFeature))]
public class AuthorizationService : IUserRolesSpec
{
private readonly IUserRepository _users;
private readonly IRoleRepository _roles;
private readonly IPermissionCache _cache;
public AuthorizationService(
IUserRepository users, IRoleRepository roles, IPermissionCache cache)
{
_users = users;
_roles = roles;
_cache = cache;
}
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public Result AssignRole(User actingUser, User targetUser, Role role)
{
if (!actingUser.Roles.Any(r => r.Name == "Admin"))
return Result.Failure("Only admins can assign roles");
if (role is null)
return Result.Failure("Role cannot be null");
// Domain logic: persist the assignment
_users.AssignRole(targetUser.Id, role.Id);
return Result.Success();
}
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.ViewerHasReadOnlyAccess))]
public Result EnforceReadOnlyAccess(User viewer, Resource resource, Operation operation)
{
var isViewer = viewer.Roles.Any(r => r.Name == "Viewer");
if (isViewer && operation != Operation.Read)
return Result.Failure($"Viewer cannot perform {operation} on {resource.Id}");
return Result.Success();
}
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.RoleChangeTakesEffectImmediately))]
public Result VerifyImmediateRoleEffect(User user, Role previousRole, Role newRole)
{
_cache.Invalidate(user.Id);
var currentPermissions = _cache.GetPermissions(user.Id);
var hasOldPermissions = currentPermissions.Intersect(previousRole.Permissions)
.Except(newRole.Permissions).Any();
if (hasOldPermissions)
return Result.Failure("Old permissions still cached after role change");
return Result.Success();
}
}namespace MyApp.Domain.Auth;
using MyApp.Specifications;
using MyApp.Requirements.Features;
/// <summary>
/// Domain service implementing user roles.
/// The compiler FORCES this class to implement all three IUserRolesSpec methods.
/// If a new AC is added to the specification interface, this class
/// will not compile until the new method is implemented.
/// </summary>
[ForRequirement(typeof(UserRolesFeature))]
public class AuthorizationService : IUserRolesSpec
{
private readonly IUserRepository _users;
private readonly IRoleRepository _roles;
private readonly IPermissionCache _cache;
public AuthorizationService(
IUserRepository users, IRoleRepository roles, IPermissionCache cache)
{
_users = users;
_roles = roles;
_cache = cache;
}
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public Result AssignRole(User actingUser, User targetUser, Role role)
{
if (!actingUser.Roles.Any(r => r.Name == "Admin"))
return Result.Failure("Only admins can assign roles");
if (role is null)
return Result.Failure("Role cannot be null");
// Domain logic: persist the assignment
_users.AssignRole(targetUser.Id, role.Id);
return Result.Success();
}
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.ViewerHasReadOnlyAccess))]
public Result EnforceReadOnlyAccess(User viewer, Resource resource, Operation operation)
{
var isViewer = viewer.Roles.Any(r => r.Name == "Viewer");
if (isViewer && operation != Operation.Read)
return Result.Failure($"Viewer cannot perform {operation} on {resource.Id}");
return Result.Success();
}
[ForRequirement(typeof(UserRolesFeature), nameof(UserRolesFeature.RoleChangeTakesEffectImmediately))]
public Result VerifyImmediateRoleEffect(User user, Role previousRole, Role newRole)
{
_cache.Invalidate(user.Id);
var currentPermissions = _cache.GetPermissions(user.Id);
var hasOldPermissions = currentPermissions.Intersect(previousRole.Permissions)
.Except(newRole.Permissions).Any();
if (hasOldPermissions)
return Result.Failure("Old permissions still cached after role change");
return Result.Success();
}
}What makes Layer 3 work:
AuthorizationService : IUserRolesSpec-- the compiler forces all three AC methods to exist with the correct signatures.[ForRequirement(typeof(UserRolesFeature))]-- type reference, not string. Ctrl+Click jumps to the feature. Refactoring tools track it.- Each method is linked to its specific AC via
nameof(UserRolesFeature.AdminCanAssignRoles)-- compiler-checked. - If someone adds AC4 to
IUserRolesSpec,AuthorizationServicefails to compile until AC4 is implemented. This is the entire point.
Layer 4: MyApp.Tests -- Type-Linked Verification
This project references MyApp.Implementation and MyApp.Specifications (and transitively, MyApp.Requirements). Test classes link to requirements through type references.
namespace MyApp.Tests.Auth;
using MyApp.Requirements.Features;
using MyApp.Specifications;
using MyApp.Domain.Auth;
[TestFixture]
[TestsFor(typeof(UserRolesFeature))]
public class UserRolesTests
{
private AuthorizationService _authService;
private User _admin;
private User _viewer;
private Role _editorRole;
[SetUp]
public void Setup()
{
_authService = new AuthorizationService(
new InMemoryUserRepository(),
new InMemoryRoleRepository(),
new InMemoryPermissionCache());
var adminRole = new Role(new RoleId("admin"), "Admin",
new HashSet<Permission> { new("manage-roles") });
var viewerRole = new Role(new RoleId("viewer"), "Viewer",
new HashSet<Permission> { new("read") });
_admin = new User(new UserId(Guid.NewGuid()), "Alice",
new HashSet<Role> { adminRole });
_viewer = new User(new UserId(Guid.NewGuid()), "Bob",
new HashSet<Role> { viewerRole });
_editorRole = new Role(new RoleId("editor"), "Editor",
new HashSet<Permission> { new("read"), new("write") });
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public void Admin_can_assign_role_to_another_user()
{
var target = new User(new UserId(Guid.NewGuid()), "Charlie", new HashSet<Role>());
var result = _authService.AssignRole(_admin, target, _editorRole);
Assert.That(result.IsSuccess, Is.True);
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public void Non_admin_cannot_assign_roles()
{
var target = new User(new UserId(Guid.NewGuid()), "Charlie", new HashSet<Role>());
var result = _authService.AssignRole(_viewer, target, _editorRole);
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.Reason, Does.Contain("Only admins"));
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.ViewerHasReadOnlyAccess))]
public void Viewer_cannot_write()
{
var resource = new Resource(new ResourceId("order-123"), "Order");
var result = _authService.EnforceReadOnlyAccess(_viewer, resource, Operation.Write);
Assert.That(result.IsSuccess, Is.False);
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.ViewerHasReadOnlyAccess))]
public void Viewer_can_read()
{
var resource = new Resource(new ResourceId("order-123"), "Order");
var result = _authService.EnforceReadOnlyAccess(_viewer, resource, Operation.Read);
Assert.That(result.IsSuccess, Is.True);
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.RoleChangeTakesEffectImmediately))]
public void Role_change_invalidates_cache_immediately()
{
var previousRole = new Role(new RoleId("viewer"), "Viewer",
new HashSet<Permission> { new("read") });
var result = _authService.VerifyImmediateRoleEffect(_admin, previousRole, _editorRole);
Assert.That(result.IsSuccess, Is.True);
}
}namespace MyApp.Tests.Auth;
using MyApp.Requirements.Features;
using MyApp.Specifications;
using MyApp.Domain.Auth;
[TestFixture]
[TestsFor(typeof(UserRolesFeature))]
public class UserRolesTests
{
private AuthorizationService _authService;
private User _admin;
private User _viewer;
private Role _editorRole;
[SetUp]
public void Setup()
{
_authService = new AuthorizationService(
new InMemoryUserRepository(),
new InMemoryRoleRepository(),
new InMemoryPermissionCache());
var adminRole = new Role(new RoleId("admin"), "Admin",
new HashSet<Permission> { new("manage-roles") });
var viewerRole = new Role(new RoleId("viewer"), "Viewer",
new HashSet<Permission> { new("read") });
_admin = new User(new UserId(Guid.NewGuid()), "Alice",
new HashSet<Role> { adminRole });
_viewer = new User(new UserId(Guid.NewGuid()), "Bob",
new HashSet<Role> { viewerRole });
_editorRole = new Role(new RoleId("editor"), "Editor",
new HashSet<Permission> { new("read"), new("write") });
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public void Admin_can_assign_role_to_another_user()
{
var target = new User(new UserId(Guid.NewGuid()), "Charlie", new HashSet<Role>());
var result = _authService.AssignRole(_admin, target, _editorRole);
Assert.That(result.IsSuccess, Is.True);
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]
public void Non_admin_cannot_assign_roles()
{
var target = new User(new UserId(Guid.NewGuid()), "Charlie", new HashSet<Role>());
var result = _authService.AssignRole(_viewer, target, _editorRole);
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.Reason, Does.Contain("Only admins"));
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.ViewerHasReadOnlyAccess))]
public void Viewer_cannot_write()
{
var resource = new Resource(new ResourceId("order-123"), "Order");
var result = _authService.EnforceReadOnlyAccess(_viewer, resource, Operation.Write);
Assert.That(result.IsSuccess, Is.False);
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.ViewerHasReadOnlyAccess))]
public void Viewer_can_read()
{
var resource = new Resource(new ResourceId("order-123"), "Order");
var result = _authService.EnforceReadOnlyAccess(_viewer, resource, Operation.Read);
Assert.That(result.IsSuccess, Is.True);
}
[Test]
[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.RoleChangeTakesEffectImmediately))]
public void Role_change_invalidates_cache_immediately()
{
var previousRole = new Role(new RoleId("viewer"), "Viewer",
new HashSet<Permission> { new("read") });
var result = _authService.VerifyImmediateRoleEffect(_admin, previousRole, _editorRole);
Assert.That(result.IsSuccess, Is.True);
}
}What makes Layer 4 work:
[TestsFor(typeof(UserRolesFeature))]-- type reference to the requirement. Source generators enumerate all tests for any feature.[Verifies(typeof(UserRolesFeature), nameof(UserRolesFeature.AdminCanAssignRoles))]-- thenameof()is compiler-checked. Rename the AC method: this updates. Delete the AC method: compile error.- Multiple tests can verify the same AC (positive and negative cases). The coverage report knows
AdminCanAssignRoleshas 2 tests. - Tests call domain code through the specification interface -- the same interface the domain was forced to implement.