> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aspfox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> Clean Architecture, CQRS with MediatR, and the reasoning behind the technical choices.

## Layer diagram

```
┌──────────────────────────────────────────────────────┐
│                      Acme.Api                        │
│   Controllers · Middleware · DI wiring · Swagger     │
│              References: Application, Infrastructure │
├──────────────────────────────────────────────────────┤
│                 Acme.Infrastructure                  │
│   EF Core · PostgreSQL · Redis · Stripe · Resend     │
│   Hangfire · TenantContext · Migrations              │
│              References: Application                 │
├──────────────────────────────────────────────────────┤
│                  Acme.Application                    │
│   Commands · Queries · Handlers · DTOs · Validators  │
│   Pipeline behaviours · Interfaces                   │
│              References: Domain                      │
├──────────────────────────────────────────────────────┤
│                    Acme.Domain                       │
│   Entities · Value objects · Enums · Constants       │
│              References: nothing                     │
└──────────────────────────────────────────────────────┘
         Dependency direction: inward only →→→
```

## What each layer does

**Domain** is the core. Entities like `User`, `Tenant`, `Subscription`, `Invitation`, `RefreshToken`, and `Notification` live here. So do the `Permissions` constants, `EmailTemplate` enum, and all domain exceptions. Domain has no NuGet dependencies beyond logging abstractions. Nothing in Domain knows about the database, HTTP, or any external service.

**Application** defines what the system does. Every operation is either a `Command` (writes data) or a `Query` (reads data). Handlers contain the actual logic. Application defines interfaces (`IApplicationDbContext`, `IEmailService`, `IStripeService`, `ICacheService`, `INotificationService`) and the Infrastructure layer implements them. This inversion is what makes Infrastructure replaceable in tests.

**Infrastructure** implements the Application interfaces. `ApplicationDbContext` implements `IApplicationDbContext`. `ResendEmailService` implements `IEmailService`. `StripeService` implements `IStripeService`. All EF Core migrations live here. Infrastructure can reference NuGet packages freely because nothing depends on it except the Api host.

**Api** wires everything together. Controllers receive HTTP requests, immediately call `MediatR.Send()`, and map the result to an HTTP response. Middleware handles authentication, tenant resolution, and subscription status. The startup registers all dependencies.

## CQRS with MediatR

Every operation flows through MediatR. Controllers never contain business logic.

```
HTTP POST /api/v1/tenants/current/invitations
    │
    ▼
InvitationsController.Invite(InviteMemberRequest request)
    │
    ├─ maps request to InviteMemberCommand
    ▼
MediatR.Send(InviteMemberCommand)
    │
    ▼
LoggingBehaviour<InviteMemberCommand, Result<InvitationDto>>
    │  logs the command name and execution time
    ▼
ValidationBehaviour<InviteMemberCommand, Result<InvitationDto>>
    │  runs InviteMemberCommandValidator (FluentValidation)
    │  returns Result.Failure(ValidationError) if invalid
    ▼
InviteMemberCommandHandler.Handle()
    │  checks permissions, creates invitation, sends email,
    │  creates notification, saves to database
    │  returns Result<InvitationDto>
    ▼
InvitationsController maps Result<T> to ApiResponse<T>
    │  Result.IsSuccess → 200 OK with data
    │  Result.IsFailure → appropriate HTTP status with error code
    ▼
HTTP 200 OK / 4xx / 5xx
```

The `Result<T>` type is a discriminated union — either success with a value, or failure with an error code and message. Controllers map results to HTTP responses in a single switch. No exceptions are thrown for expected business failures.

## API response envelope

Every response from the API uses the same shape:

**Success:**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "user@example.com",
    "name": "Jane Smith"
  }
}
```

**Validation failure (400):**

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "email": ["'Email' must not be empty.", "'Email' is not a valid email address."],
      "name": ["'Name' must not be empty."]
    }
  }
}
```

**Business failure (401, 403, 404, 409, etc.):**

```json theme={null}
{
  "success": false,
  "error": {
    "code": "TOKEN_REUSE_DETECTED",
    "message": "This refresh token has already been used. Your session has been revoked."
  }
}
```

The `code` field is a machine-readable string. See the [Error Codes](/reference/error-codes) reference for every possible value.

## Technical decisions

**PostgreSQL over SQL Server** — no licensing cost, excellent EF Core support, row-level security features, and the most common database choice for .NET on Linux. SQL Server requires a license in production.

**RS256 (asymmetric JWT) over HS256 (symmetric)** — with HS256, every service that verifies tokens needs the secret key. With RS256, services verify using only the public key. When you add a microservice, an edge function, or a third-party integration that needs to validate AspFox tokens, it needs only the public key — never the private key.

**Mapster over AutoMapper** — Mapster is faster, requires no profile registration, and handles most mappings with zero configuration. AutoMapper's profile system adds boilerplate that provides no benefit for a codebase this size.

**Resend over SendGrid** — cleaner API, better developer experience, generous free tier (100 emails/day), and a dashboard that shows the full rendered HTML of every send. SendGrid's API is significantly more complex for the same operations.

**Zustand over Redux** — Redux is right for complex shared state with many consumers and strict update rules. Zustand's store surface in AspFox (auth state, tenant context, theme preference) does not justify Redux's ceremony.

**TanStack Query** — best-in-class cache invalidation and mutation handling. The stale-while-revalidate model and query key-based invalidation eliminate most of the async state management complexity from React components.
