πŸ—ΊοΈ Architecture Overview

Table of contents

Solution structure

1
2
3
4
5
6
7
8
9
10
grimoire-api/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Grimoire.Core            ← Domain layer
β”‚   β”œβ”€β”€ Grimoire.Infrastructure  ← Data + services layer
β”‚   β”œβ”€β”€ Grimoire.Api             ← HTTP API layer
β”‚   └── Grimoire.Consumer        ← Client library
└── tests/
    β”œβ”€β”€ Grimoire.Tests            ← Unit tests
    β”œβ”€β”€ Grimoire.IntegrationTests ← Integration tests
    └── Grimoire.E2eTests         ← End-to-end tests

Project dependency graph

1
2
3
4
5
6
Grimoire.Api
  β”œβ”€β”€ Grimoire.Core           (entities, interfaces)
  └── Grimoire.Infrastructure (EF Core, encryption, repositories)
        └── Grimoire.Core

Grimoire.Consumer            (standalone β€” no dependency on Api or Infrastructure)

Grimoire.Consumer is intentionally isolated. It communicates with the API over HTTP and has no compile-time coupling to server-side code.


Layer responsibilities

Grimoire.Core

The innermost layer. Contains only:

No framework references. No EF Core. No dependency injection.

Grimoire.Infrastructure

Implements the Core interfaces:

Grimoire.Api

The ASP.NET Core 10 host:

Grimoire.Consumer

A standalone .NET library for consuming the API:


Request pipeline

Every HTTP request flows through:

1
2
3
4
5
6
7
8
9
10
11
12
13
Request
  β”‚
  β”œβ”€β–Ί Serilog request logging
  β”œβ”€β–Ί Exception handler (ProblemDetails)
  β”œβ”€β–Ί CORS
  β”œβ”€β–Ί AdminApiKeyMiddleware
  β”‚     (validates Bearer token for /api/management/*)
  β”œβ”€β–Ί ConsumerApiKeyMiddleware
  β”‚     (validates X-Api-Key for /api/consumer/*)
  β”‚     (stores matched Application in HttpContext.Items)
  └─► Controllers
        β”œβ”€β–Ί Management controllers (FluentValidation, repository calls)
        └─► Consumer controllers (repository + decryption)

Authentication short-circuit

Both middleware components return 401 Unauthorized immediately if:

They do not call next() in the rejection path, so no controller code runs.

Consumer identity propagation

ConsumerApiKeyMiddleware resolves the Application entity from the database, verifies the API key hash, and stores the entity in HttpContext.Items["GrimoireApplication"]. Consumer controllers read it from there rather than re-querying the database.


Database migrations

EF Core migrations are stored in src/Grimoire.Infrastructure/Persistence/Migrations/.

On startup, Program.cs runs:

1
2
3
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<GrimoireDbContext>();
db.Database.Migrate();

This applies any pending migrations automatically. The SQLite file is created on first run.


Swagger UI

The API exposes two separate Swagger documents at runtime:

Doc URL Audience
Management /swagger/management/swagger.json Administrators
Consumer /swagger/consumer/swagger.json Application developers

The Swagger UI is available at /swagger in Development environment only.