# Architecture Standards

> **Scope:** universal
> **Layer:** 0 (always load)
> **Keywords:** architecture, clean, solid, layers, di, separation of concerns
> **Load When:** always

Architecture patterns, layer responsibilities, and structural rules.

---

## Clean Architecture Layers

```
┌───────────────────────────────────────┐
│ Web (Blazor/NextJS, API)              │ ← Entry, UI, controllers
│ ┌─────────────────────────────────┐   │
│ │ Infrastructure (EF, External)   │   │ ← Data, services
│ │ ┌───────────────────────────┐   │   │
│ │ │ Application (Services)    │   │   │ ← Business logic
│ │ │ ┌───────────────────┐     │   │   │
│ │ │ │ Domain (Entities) │     │   │   │ ← Core
│ │ │ └───────────────────┘     │   │   │
│ │ └───────────────────────────┘   │   │
│ └─────────────────────────────────┘   │
└───────────────────────────────────────┘
```

**Dependency Rule:** Inner layers NEVER reference outer layers.

---

## Layer Responsibilities

| Layer | Contains | References | Key Packages |
|-------|----------|------------|--------------|
| **Domain** | Entities, Value Objects, Enums, Interfaces | None | None (pure C#) |
| **Application** | Services, DTOs, Validators | Domain | FluentValidation, MediatR |
| **Infrastructure** | DbContext, Repositories, External clients | Domain, Application | EF Core, HttpClient |
| **Web** | Pages, Components, Controllers, Program.cs | All | Blazor, Fluent UI |

---

## Project Structure

```
src/{App}.Domain/
  Entities/         Order.cs, Customer.cs
  ValueObjects/     Money.cs, Address.cs
  Enums/            OrderStatus.cs
  Interfaces/       IOrderRepository.cs

src/{App}.Application/
  Services/         OrderService.cs
  DTOs/             OrderResponse.cs
  Validators/       CreateOrderValidator.cs

src/{App}.Infrastructure/
  Data/
    AppDbContext.cs
    Configurations/  OrderConfiguration.cs
  Repositories/      OrderRepository.cs
  Services/          AsaasPaymentService.cs

src/{App}.Web/
  Components/
    Layout/          MainLayout.razor
    Pages/           Orders.razor
  wwwroot/
  Program.cs
```

---

## File Organization

| Rule | Description |
|------|-------------|
| One class per file | `OrderService.cs` for `OrderService` |
| Folder = namespace | `Entities/Order.cs` → `MyApp.Domain.Entities` |
| No nested classes | Except private helpers |
| Extensions per layer | `ServiceCollectionExtensions.cs` in each project |

---

## SOLID Quick Reference

| Principle | Rule | Signal |
|-----------|------|--------|
| **S** Single Responsibility | One reason to change | Class > 300 lines? Mix DB + email + validation? Split. |
| **O** Open/Closed | Extend via new classes | `switch` on type? Use polymorphism. |
| **L** Liskov Substitution | Subtypes substitutable | Override throws `NotSupportedException`? Violation. |
| **I** Interface Segregation | Small interfaces | Interface with 10+ methods? Split. |
| **D** Dependency Inversion | Depend on abstractions | Concrete class in constructor? Use interface. |

---

## Service Patterns

| Pattern | When | Complexity |
|---------|------|-----------|
| Direct DbContext | Simple CRUD, < 3 entities | Low |
| Repository | Data abstraction, testability | Medium |
| Repository Factory | Background ops, Hangfire | Medium |
| CQRS | Read/write models differ | Medium-High |
| MediatR | Decouple handlers, pipelines | Medium |

```csharp
public sealed class OrderService(IOrderRepository repository, ILogger<OrderService> logger) : IOrderService
{
    public async Task<Result<OrderResponse>> CreateAsync(CreateOrderRequest request, CancellationToken ct = default)
    {
        logger.LogInformation("Creating order for {CustomerId}", request.CustomerId);
        if (request.Items.Count == 0)
            return Result.Failure<OrderResponse>("Order must have items");

        var order = Order.Create(request.CustomerId, request.Items);
        await repository.AddAsync(order, ct);
        await repository.SaveChangesAsync(ct);
        return Result.Success(order.ToResponse());
    }
}
```

---

## DI Registration Pattern

```csharp
// Program.cs
builder.Services
    .AddApplication()
    .AddInfrastructure(builder.Configuration)
    .AddWeb();

// Application/Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddApplication(this IServiceCollection services)
{
    services.AddScoped<IOrderService, OrderService>();
    services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();
    return services;
}

// Infrastructure/Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config)
{
    var conn = config.GetConnectionString("Default");
    services.AddDbContext<AppDbContext>(o => o.UseSqlServer(conn));
    services.AddDbContextFactory<AppDbContext>(o => o.UseSqlServer(conn));
    services.AddScoped<IOrderRepository, OrderRepository>();
    return services;
}
```

---

## Architecture Smells

| Smell | Signal | Fix |
|-------|--------|-----|
| Circular dependency | A refs B, B refs A | Extract shared interface to lower layer |
| Fat controller/page | Page > 50 lines logic | Extract to service |
| Anemic domain | Entities data-only | Move behavior into entities |
| God class | > 500 lines, 10+ deps | Split by responsibility |
| Service locator | `IServiceProvider.GetService<T>()` | Constructor injection |
| Over-abstraction | Interface, one impl, never mocked | Use concrete class |
| Under-abstraction | External service called directly | Extract interface |
| Leaking infrastructure | `DbContext` in Web layer | Route through Application |

---

## Checklist

- [ ] No circular references (`dotnet build` clean)
- [ ] Domain has zero NuGet packages
- [ ] No unused project references
- [ ] Every interface has consumer (no "just in case")
- [ ] External services through interfaces
- [ ] Configuration via Options pattern
- [ ] No hardcoded secrets in code
- [ ] All secrets in Key Vault or user-secrets

---

*MORPH-SPEC by Polymorphism Tech*
