OpenSpec: Refactor request router into middleware pipeline#22
Merged
yetanotherchris merged 7 commits intomainfrom Feb 28, 2026
Merged
OpenSpec: Refactor request router into middleware pipeline#22yetanotherchris merged 7 commits intomainfrom
yetanotherchris merged 7 commits intomainfrom
Conversation
Decompose DynamoDbRequestRouter into focused classes using ASP.NET Core middleware pipeline: - HealthCheckHandler: standalone GET / endpoint - DynamoDbErrorMiddleware: cross-cutting error formatting - DynamoDbValidationMiddleware: HTTP/header validation - DynamoDbRequestRouter: pure operation dispatch Includes proposal, design, and tasks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Simplify design — the health check is a one-liner, a dedicated HealthCheckHandler class is overkill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace MapGet with AddHealthChecks() + MapHealthChecks('/') and a
custom ResponseWriter. Extensible for future IHealthCheck implementations.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Map health checks to both / (backward compat) and /healthz (standard convention) using shared HealthCheckOptions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Assign context.Request.Body to a local var instead of repeating it 14 times in the switch expression - Router reads X-Amz-Target header directly instead of using HttpContext.Items magic string dictionary - Validation middleware still validates the header; router just parses it Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce IDynamoDbCommand interface + DynamoDbCommand<TRequest,TResponse> abstract base class. Each operation becomes a typed command class that owns its own deserialization and serialization. Router becomes a dictionary dispatcher with no knowledge of specific operations. 14 command classes grouped into 5 files matching existing operation class grouping. Router constructor changes from 5 operation class params to IEnumerable<IDynamoDbCommand>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add Design Feedback section recording five changes made during review: - Drop HealthCheckHandler class (inline into Program.cs) - Use built-in AddHealthChecks()/MapHealthChecks() framework - Add /healthz alongside / - Remove HttpContext.Items for operation name (read header directly) - Replace Dispatch<TReq,TRes> switch with Command pattern Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OpenSpec: Refactor Request Router
Problem
DynamoDbRequestRouterhandles 5 responsibilities in one class: health check, HTTP validation, operation dispatch, JSON serialization, and error formatting. This violates SRP and makes the class the dumping ground for unrelated changes.Proposed Solution
Decompose into focused classes using ASP.NET Core's middleware pipeline:
HealthCheckHandlerGET /endpoint viaMapGetDynamoDbErrorMiddlewareDynamoDbException,JsonException,TransactionCanceledException)DynamoDbValidationMiddlewareX-Amz-Targetheader parsingDynamoDbRequestRouterPipeline
GET / → HealthCheckHandler (short-circuits) POST / → ErrorMiddleware → ValidationMiddleware → Router (dispatch)Key Decisions
Files
See
openspec/changes/refactor-request-router/for full proposal, design, and tasks.Design Feedback
The following changes were made to the initial proposal following review by Chris:
Health check: drop the
HealthCheckHandlerclassThe initial draft proposed a
HealthCheckHandlerstatic class with aMapHealthCheckextension method. Chris pointed out this is a one-liner and doesn't warrant a separate class — it should live directly inProgram.cs. Accepted: health check is now aMapHealthCheckscall inline inProgram.cs.Health check: use the built-in ASP.NET Core framework
Chris noted that ASP.NET Core has built-in health check extension methods (
AddHealthChecks()/MapHealthChecks()). The initial draft used a rawMapGet. Accepted: switched tobuilder.Services.AddHealthChecks()+app.MapHealthChecks()with a customResponseWriter, which is more extensible and idiomatic.Health check: add
/healthzChris requested the health check also respond at the standard
/healthzpath, in addition to/. Accepted: both paths are mapped with a sharedHealthCheckOptionsinstance.HttpContext.Itemsfor operation name: remove itThe initial design stored the parsed operation name in
context.Items["DynamoDb.Operation"]in the validation middleware and read it back in the router. Chris flagged this as unpleasant (a stringly-typed dictionary lookup with a cast). Accepted: the router readsX-Amz-Targetdirectly — the validation middleware has already guaranteed it is valid, so re-parsing is safe and clearer.Command pattern instead of
Dispatch<TReq, TRes>The initial design retained a generic
Dispatch<TReq, TRes>(Stream body, Func<TReq, TRes> handler)method inside the router, with a 14-arm switch expression passing operation methods asFuncdelegates. Chris observed this was an unnecessarily dynamic/functional style for C# — each operation should be a proper typed class. Accepted: replaced with anIDynamoDbCommandinterface, aDynamoDbCommand<TRequest, TResponse>abstract base class handling serialization, and 14 concrete command classes. The router becomes a dictionary dispatcher with no knowledge of specific operations.