---
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
- Configuration
- Operations and processes
- [Business rules](business-rules.md)
- Data model
- [Operational runbook](operational-runbook.md)
- Tech debt

## 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`
