RTS questionnaire · Q37 · API cross-cutting concerns proof

Have you worked with API versioning, authentication/authorization, exception handling, validation, logging, and observability in .NET APIs?

The six concerns the question lists, each stated as a rule rather than as a technology: what is decided once at the edge, what a client is allowed to learn, and what the server must record.

Status: reviewed Live API cross-cutting workflow Versioning · Authz · Validation · Errors · Observability Jump to live proof ↓

37. Versioning, authz, validation, errors, logging and observability

Answer as submitted

“Yes — these six concerns are wired once in the composition root rather than recalled at each endpoint.”
Technical decision: expand. The submitted line claims the six concerns without showing a decision behind any of them. This page is the separate, detailed version: what is decided once instead of per endpoint, what a client is allowed to learn when something fails, where each validation rule lives, and what the server must record so a failure is debuggable without being leaked.

Expanded answer

Yes, and the organising principle is that a cross-cutting concern applied per endpoint is a concern that will be forgotten at one endpoint. Versioning: the version lives in the URL because that is what a client pins and can see in its logs, the api-version header is accepted as a second route for internal callers behind a gateway, and a missing version is a 400 rather than a silent default — because AssumeDefaultVersionWhenUnspecified looks helpful and is exactly how a new client binds to v1 forever. Deprecation is announced with a Sunset header plus telemetry on who is still calling it, and the OpenAPI document is generated per version. Authentication and authorization: a JWT validated against the issuer's JWKS, cached and refreshed on a key-id change, with a named policy per endpoint and a fallback policy so that a new endpoint is protected by default — the failure mode of forgetting should be an endpoint nobody can call, not one everybody can. Identity comes from validated claims and never from the payload, and a resource belonging to another tenant answers 404 rather than 403 so existence is not disclosed. Validation is three layers with three jobs: the edge checks shape and format and returns every failure at once, the domain checks the business rules and can only be reached through its aggregate, and the database keeps the constraint that survives two concurrent requests. A rule duplicated across layers is a bug. Exception handling is one handler for the pipeline that maps known failures to the right status with a machine-readable problem type, and turns an unexpected one into a 500 carrying a trace id and nothing else — no stack trace, no SQL text, no internal path. Known failures are mapped rather than caught, and expected outcomes log at Information so a real incident is not buried in anticipatable noise. Logging and observability: structured logs inside a correlation scope opened at the first middleware and closed even on failure, the same id propagated through every call and message, OpenTelemetry traces that carry it, metrics that answer rate/errors/duration per endpoint with high-cardinality dimensions deliberately forbidden, personal data redacted at the sink rather than at each call site, and health checks that separate liveness from readiness — because a liveness probe that checks a dependency restarts healthy pods when the dependency is down, which turns a partial outage into a total one.

ConcernDecisionFailure it prevents
VersioningURL primary, header accepted, no silent default, Sunset on deprecationa client silently bound to v1 forever, and a breaking change nobody was warned about
AuthenticationJWT validated against the issuer JWKS, cached, refreshed on key rotationa key rotation that takes the API down, or validation disabled for speed
Authorizationa named policy per endpoint plus a fallback policy, claims never the payloadan endpoint added in a hurry that anyone can call
Validationedge for shape, domain for rules, database constraint as the last defencerules duplicated in three places, drifting apart with each change
Exception handlingone handler, mapped failures, 500 with a trace id and no internalsstack traces and SQL text reaching a client, or a 500 nobody can trace
Logging and observabilitystructured logs in a correlation scope, traces, RED metrics, redaction at the sinkan incident investigated by guessing, and PII in a log index

Live API cross-cutting workflow proof

The running component below changes its concern/result view through real event binding. Select a concern to see the decision, the reasoning behind it, and the evidence that it holds.

LIVE PREVIEW
RUNNING
API CROSS-CUTTING WORKFLOWRUNNING

Choose a cross-cutting concern

Select a concern to inspect the decision, the reasoning and the evidence.

$ /api/v1/orders (primary) · api-version header (also supported) · no version → 400 · Sunset header on deprecated versions

The version lives in the URL because it is the version clients actually pin to, with the header accepted as a second route for internal callers. A request without a version is a 400 rather than a silent default, which is what stops a new client from accidentally binding to v1 forever. Deprecation is announced with a Sunset header plus telemetry on the callers still using it, and the OpenAPI document is generated per version.

versions served concurrently: 2
requests without a version: 400, never a silent default
callers on the deprecated version: tracked, by client id
deprecation announced with a Sunset header: yes

What wiring the concerns once buys

Rules I apply. Decide the concern once in the composition root, because a concern applied per endpoint is one that gets forgotten at one endpoint; make the default safe so that forgetting produces an endpoint nobody can call rather than one everybody can; never let the caller learn more than it can act on, and keep the diagnostic detail on the server with a trace id to connect the two; give every validation rule exactly one home and let the other layers only defend it; make the expected outcomes cheap to log and the unexpected ones expensive, so the signal is not drowned by the anticipatable; forbid high-cardinality dimensions in metrics, which is what makes a metric readable and affordable; and give every alert a dashboard, a runbook and an owner, or delete it.
Boundary for Q37: this page covers the six cross-cutting concerns in an ASP.NET Core API. The REST resource model, HTTP semantics and contract testing are on the Q28 proof page, and the OAuth2/OIDC token validation and policy details are on the Q08 proof page.

Source files

Select a file to inspect the proof with syntax highlighting and line numbers. The first file is the code running the component above; the others are the composition root that wires versioning, authz and validation, the single exception handler with its mapping table, and the observability configuration.

Acceptance criteria