Skip to main content

Introduction

SAPFIAI is built using Clean Architecture principles, creating a maintainable, testable, and scalable API solution. The architecture separates concerns into distinct layers, with dependencies flowing inward toward the domain core.

Architecture Layers

The solution is organized into four main layers:

Domain Layer

Core business logic and entities. No external dependencies.

Application Layer

Use cases, CQRS commands/queries, and business orchestration.

Infrastructure Layer

Data persistence, external services, and third-party integrations.

Web Layer

API endpoints, middleware, and HTTP concerns.

Dependency Flow

The architecture enforces a strict dependency rule:
Web Layer → Infrastructure Layer → Application Layer → Domain Layer
Dependencies point inward. The Domain layer has zero external dependencies, while outer layers depend on inner layers through abstractions (interfaces).

Visual Representation

Layer Interactions

Request Flow Example

Let’s follow a typical API request through the layers:
1

API Request

Client sends a POST request to /api/permissions to create a new permission.
2

Web Layer

The endpoint receives the request and sends a CreatePermissionCommand via MediatR.
// src/Web/Endpoints/Permissions.cs:83-86
private static async Task<IResult> CreatePermission(
    IMediator mediator, 
    [FromBody] CreatePermissionCommand command)
{
    var result = await mediator.Send(command);
    return result.ToCreatedResult(id => $"/api/permissions/{id}");
}
3

Application Layer

MediatR routes the command to its handler, which contains the use case logic.
// src/Application/Permissions/Commands/CreatePermission
public class CreatePermissionCommandHandler 
    : IRequestHandler<CreatePermissionCommand, Result<int>>
{
    public async Task<Result<int>> Handle(...)
    {
        // Business logic here
        var permission = new Permission { ... };
        _context.Permissions.Add(permission);
        await _context.SaveChangesAsync(cancellationToken);
        return Result.Success(permission.Id);
    }
}
4

Infrastructure Layer

The ApplicationDbContext persists the entity to the database.
// src/Infrastructure/Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Permission> Permissions => Set<Permission>();
}
5

Domain Layer

The Permission entity defines the business rules and data structure.
// src/Domain/Entities/Permission.cs
public class Permission : BaseEntity
{
    public string Name { get; set; }
    public string Module { get; set; }
    public bool IsActive { get; set; }
}

Key Benefits

Each layer can be tested independently:
  • Domain: Pure unit tests with no mocking
  • Application: Test use cases with mocked dependencies
  • Infrastructure: Integration tests with real databases
  • Web: Functional tests for end-to-end scenarios
  • Clear separation of concerns
  • Each layer has a single responsibility
  • Easy to locate and modify code
  • Changes in one layer rarely affect others
  • Swap implementations without changing business logic
  • Easy to switch databases (SQL Server → PostgreSQL)
  • Change UI frameworks without touching the core
  • Add new features without modifying existing code
  • Independent deployment of layers
  • Microservices-ready architecture
  • Horizontal scaling of specific components
  • Performance optimization per layer

Technology Stack

The architecture leverages modern .NET technologies:
LayerTechnologies
Domain.NET 8, C# Records
ApplicationMediatR, FluentValidation, AutoMapper
InfrastructureEntity Framework Core 8, SQL Server, ASP.NET Identity
WebASP.NET Core 8, Minimal APIs, Swagger/OpenAPI

Cross-Cutting Concerns

Several patterns span multiple layers:

MediatR Pipeline Behaviors

The application uses pipeline behaviors for cross-cutting concerns:
// src/Application/DependencyInjection.cs:16-19
services.AddMediatR(cfg => {
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(AuthorizationBehaviour<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>));
});
These behaviors automatically handle:
  • Exception handling: Catches and logs unhandled exceptions
  • Authorization: Validates user permissions before execution
  • Validation: Runs FluentValidation rules automatically
  • Performance: Logs slow requests for monitoring
Pipeline behaviors run for every MediatR request, providing consistent behavior across all use cases.

Next Steps

Clean Architecture

Deep dive into Clean Architecture principles

Project Structure

Detailed breakdown of folders and files

CQRS Pattern

Learn about Commands and Queries

Getting Started

Start building with SAPFIAI

Build docs developers (and LLMs) love