Example: API architecture¶
architecture.md records how the service is hosted, how a request travels from
IIS to the database, what each layer is responsible for, and — the section that
matters most — where dependency injection is actually wired up. Doc gen produces
one per API at docs/apis/<api-slug>/architecture.md.
What reviewers should check¶
- The request pipeline diagram uses a standard
```mermaidfence, and its nodes are the components this repository really has. A node labelled "none" means the author kept a template node instead of deleting it. - The composition root table names the file exactly as it is spelled on disk.
Doc gen finds it behaviourally —
new ContainerBuilder(),RegisterType<…>().As<…>(),AutofacWebApiDependencyResolver— and traces it back to theApplication_Startchain.App_Start\IoCConfig.csasserted by convention is the classic defect here. - Registration lifetimes are stated per registration.
InstancePerRequestfor a unit of work andSingleInstancefor a factory mean very different things, so a blank or uniform lifetime column is suspect. - The routing table matches
WebApiConfig.Register: whetherMapHttpAttributeRoutes()is called, whether a convention template survives, and what the route prefixes are. Resolved routes themselves belong on the endpoint pages, not here. - The base controller section says what the local base class adds, and the table makes clear that the behaviour applies to every derived controller.
- Error handling names the mechanism that is present rather than assuming one, and states plainly whether exception detail crosses the service boundary.
- The Swagger and telemetry tables distinguish "absent" from "present but commented out" — those have different operational consequences.
Example¶
The document below is a complete, correct api-architecture.md for a fictional
API.
View rendered example — the same document as a real page, so you can compare rendering as well as source.
---
title: Architecture
summary: Hosting model, request pipeline, layering, and the dependency injection composition root.
status: draft
owner: Agency Systems Team
last_reviewed: 2026-09-10
---
# Architecture
`SAIF.Agency.Api` is a conventional three-layer ASP.NET Web API 2 service hosted
in IIS. Controllers bind and authorise the request, a service layer holds the
agency rules, and NHibernate maps the `AGENCY` schema behind an `IUnitOfWork`
scoped to the request. Autofac is the composition root and is registered before
routing during `Application_Start`, so every controller is constructor-injected
and no controller resolves a service itself.
## Hosting model
The service is hosted in IIS. `Global.asax` instantiates `AgencyApiApplication` :
`System.Web.HttpApplication`, whose `Application_Start` runs the `App_Start` configuration classes, including `WebApiConfig.Register(HttpConfiguration)`.
| Aspect | Finding |
| --- | --- |
| Host | IIS (`System.Web.HttpApplication`) — no OWIN, no WCF, no self-host |
| Application class | `AgencyApiApplication` (`src/SAIF.Agency.Api/Global.asax.cs`) |
| Startup call chain | `Application_Start` → `AutofacConfig.Register` → `GlobalConfiguration.Configure(WebApiConfig.Register)` → `SwaggerConfig.Register` → `ConfigureOpenTelemetry` |
| Virtual directory / app pool | Site `agencyapi`, virtual directory `/agency`, application pool `AgencyApiPool` (.NET CLR v4.0, Integrated) |
| Target framework | .NET Framework `4.8` |
## Request pipeline
```mermaid
flowchart TD
CLIENT["Client request<br/>SAIF.Agency.Api.Client or agency portal"] --> IIS["IIS + AgencyApiApplication<br/>Windows authentication"]
IIS --> HANDLER["AgencyApiCorrelationHandler<br/>DelegatingHandler"]
HANDLER --> ROUTE["Attribute routing<br/>MapHttpAttributeRoutes()"]
ROUTE --> AUTH["SaifAuthorize filter<br/>role check"]
AUTH --> CTRL["AgenciesController : AgencyApiBase"]
CTRL --> SVC["AgencyService<br/>business rules"]
SVC --> UOW["NHibernate IUnitOfWork<br/>IUnitOfWorkFactory('oracle')"]
UOW --> DB[(Oracle AGENCY schema)]
CTRL --> ERR["AgencyApiExceptionFilter<br/>global exception filter"]
```
The service makes no downstream HTTP calls, so no `SAIF.Web.API.Clients` node
appears in the pipeline: every response is assembled from the `AGENCY` schema.
## Layer responsibilities
| Layer | Type | Responsibility |
| --- | --- | --- |
| `AgencyApiApplication` | `System.Web.HttpApplication` | Application start-up and configuration wiring |
| `App_Start\WebApiConfig.cs` | Configuration | Attribute route mapping, XML formatter removal, JSON serializer settings, registration of `AgencyApiCorrelationHandler` and `AgencyApiExceptionFilter` |
| `AgenciesController` | `AgencyApiBase` : `ApiController` | Request binding, role checks, delegation to the service layer, result construction |
| `AgencyService` | Service | Agency lookup, search filtering, roster assembly, and contact update rules (BR-001 to BR-008) |
| `AgencyRepository` | NHibernate | Persistence through `IUnitOfWork` against the `AGENCY` schema |
## Routing configuration
| Aspect | Finding |
| --- | --- |
| `MapHttpAttributeRoutes()` called | Yes |
| Convention route templates | None — the default `api/{controller}/{id}` route is commented out in `WebApiConfig.Register` |
| Route prefix / versioning | `[RoutePrefix("v1/agencies")]`, `[RoutePrefix("v1/agents")]`, `[RoutePrefix("v1/appointments")]`; controllers live under `Controllers\v1\` |
| Controller folder layout | `Controllers\AgencyApiBase.cs` at the root, versioned controllers in `Controllers\v1\` |
## Base controller
All three controllers derive from `AgencyApiBase` : `ApiController` rather than
from `ApiController` directly. The base class resolves the caller's Windows
identity into an `AgencyCaller` record used for audit stamping, exposes
`NotFoundForAgency(agencyNumber)` and `ValidationFailure(result)` helpers that
produce the service's standard `ApiErrorDto` body, and writes an `ILogService`
entry carrying the correlation id added by `AgencyApiCorrelationHandler`. Every
derived controller inherits that behaviour, so error bodies and audit
attribution are identical across the API.
| Controller | Base | Inherited behavior |
| --- | --- | --- |
| `AgenciesController` | `AgencyApiBase` | Caller resolution, `ApiErrorDto` construction, correlated request logging |
| `AgentsController` | `AgencyApiBase` | Caller resolution, `ApiErrorDto` construction, correlated request logging |
| `AppointmentsController` | `AgencyApiBase` | Caller resolution, `ApiErrorDto` construction, correlated request logging |
## Composition root
| Field | Value |
| --- | --- |
| **Project** | `SAIF.Agency.Api` |
| **File** | `src/SAIF.Agency.Api/App_Start/AutofacConfig.cs` |
| **Entry method** | `AutofacConfig.Register` |
| **Invoked from** | `AgencyApiApplication.Application_Start`, first statement, before `GlobalConfiguration.Configure(WebApiConfig.Register)` |
| **Resolver** | `AutofacWebApiDependencyResolver` assigned to `GlobalConfiguration.Configuration.DependencyResolver` |
| **Autofac version** | `4.9.4` (`Autofac.WebApi2` 4.3.1) |
| **Additional registration files** | `src/SAIF.Agency.Api/DependencyModules/AgencyServiceModule.cs` |
`AutofacConfig.Register` builds a single `ContainerBuilder`. It calls
`RegisterCommonDependencies("agencyDb", "AGENCY")` from `SAIF.Web.Mvc.Extensions`,
which supplies `ILogService`, the Environment API configuration reader, and the
NHibernate session factory configured for the named Oracle database and schema;
the FluentNHibernate class maps are picked up by scanning the
`SAIF.Agency.Api.Data` assembly. It then registers the API's own controllers with
`RegisterApiControllers(Assembly.GetExecutingAssembly())`, loads
`AgencyServiceModule` for the service and repository registrations, and finally
builds the container and assigns the resolver.
| Registration | Lifetime | Resolved by | Purpose |
| --- | --- | --- | --- |
| `IAgencyService` → `AgencyService` | InstancePerRequest | `AgenciesController` | Agency lookup, search, roster, and contact update rules |
| `IAgentService` → `AgentService` | InstancePerRequest | `AgentsController`, `AgencyService` | Agent lookup and appointment filtering |
| `IAppointmentService` → `AppointmentService` | InstancePerRequest | `AppointmentsController` | Appointment reads and creation |
| `IAgencyRepository` → `AgencyRepository` | InstancePerRequest | `AgencyService` | NHibernate queries and writes against the `AGENCY` schema |
| `IContactValidator` → `ContactValidator` | InstancePerLifetimeScope | `AgencyService` | Email and phone validation supplied by `SAIF.Framework.Validation` |
| `IUnitOfWorkFactory("oracle")` | SingleInstance | `IUnitOfWork` resolution | NHibernate unit of work factory for the Oracle `AGENCY` schema |
| `IUnitOfWork` | InstancePerRequest | `AgencyRepository` | Session and transaction scope for one request |
| `ILogService` | SingleInstance | `AgencyApiBase`, `AgencyApiExceptionFilter` | Structured application logging |
## Error handling
| Mechanism | Registered in | Scope | Behavior |
| --- | --- | --- | --- |
| `AgencyApiExceptionFilter` : `ExceptionFilterAttribute` | `src/SAIF.Agency.Api/App_Start/WebApiConfig.cs` (`config.Filters.Add`) | Global | Maps `AgencyNotFoundException` to `404 Not Found`, `AgencyValidationException` to `400 Bad Request`, `AgencyStateConflictException` and `StaleAgencyVersionException` to `409 Conflict`, and anything else to `500 Internal Server Error`. Writes an `ApiErrorDto` carrying `code`, `message`, and the correlation id, and logs the full exception through `ILogService`. |
| `AgencyApiCorrelationHandler` : `DelegatingHandler` | `src/SAIF.Agency.Api/App_Start/WebApiConfig.cs` (`config.MessageHandlers.Add`) | Global | Adds or echoes the `X-Correlation-Id` header on every request and response so a failure can be traced from a consumer's log to this service's log. Does not translate exceptions. |
| Per-action `try`/`catch` | `src/SAIF.Agency.Api/Controllers/v1/AgenciesController.cs` (`UpdateAgencyContact`) | Per-action | Catches `StaleAgencyVersionException` to add the current version to the `409` body before rethrowing for the filter to format. |
A caller never receives exception detail. `ApiErrorDto` carries a stable error
code, a message written for the caller, and the correlation id; the stack trace
and the Oracle error text stay in the `ILogService` entry.
## API documentation surface
| Aspect | Finding |
| --- | --- |
| Swagger / Swashbuckle | Active (`SwaggerConfig.Register` called from `Application_Start`) |
| Exposed route | `/swagger/ui/index` |
| Package and version | `Swashbuckle` `5.6.0` |
## Telemetry
| Aspect | Finding |
| --- | --- |
| Instrumentation | `ConfigureOpenTelemetry(o => o.Owner = "Agency Systems Team")` |
| Registered in | `src/SAIF.Agency.Api/Global.asax.cs` |
## Design constraints
- Attribute routing only: a new endpoint is invisible unless it carries a
`[Route]` attribute, because no convention route exists to fall back on.
- Every request runs inside one `IUnitOfWork`. A controller action that needs
two independent transactions has no supported way to express that.
- Controllers must derive from `AgencyApiBase`; a controller derived directly
from `ApiController` would produce a different error body and would not stamp
the correlation id.
- The service holds no cache. Every read is an Oracle round trip, which keeps
agency data current at the cost of load on the `AGENCY` schema.
## Integration points
| System | Type | Purpose |
| --- | --- | --- |
| Oracle `AGENCY` schema | Database | Agency, agent, appointment, and contact audit records |
| Environment API | Configuration service | Supplies the `agencyDb` connection details and environment-specific settings at start-up |
## Related documentation
- [API index](index.md)
- [Overview](overview.md)
- [Endpoints](endpoints.md)
- [Authentication and authorization](authentication-authorization.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Data model](data-model.md)
- [Operational runbook](operational-runbook.md)
- [Tech debt](tech-debt.md)
## Sources
- `src/SAIF.Agency.Api/Global.asax.cs`
- `src/SAIF.Agency.Api/App_Start/AutofacConfig.cs`
- `src/SAIF.Agency.Api/App_Start/WebApiConfig.cs`
- `src/SAIF.Agency.Api/App_Start/SwaggerConfig.cs`
- `src/SAIF.Agency.Api/DependencyModules/AgencyServiceModule.cs`
- `src/SAIF.Agency.Api/Controllers/AgencyApiBase.cs`
- `src/SAIF.Agency.Api/Filters/AgencyApiExceptionFilter.cs`
- `src/SAIF.Agency.Api/Handlers/AgencyApiCorrelationHandler.cs`
- `src/SAIF.Agency.Api/packages.config`