Summary
Add support for registering middleware from classes implementing an ILambdaMiddleware interface, providing a cleaner, testable, and more maintainable alternative to inline delegate-based middleware. This feature may include source generation for enhanced performance and compile-time validation.
Problem Statement
Currently, middleware can only be registered using inline delegates via UseMiddleware() or as static extension methods. This approach has several limitations:
- Testability: Difficult to unit test complex middleware logic in isolation
- Maintainability: Inline delegates become hard to read and maintain for complex middleware
- Dependency Injection: No built-in way to inject services directly into middleware
- Discoverability: Middleware logic is scattered across extension methods and inline code
- Reusability: Hard to share middleware implementations across different Lambda applications
Proposed Solution
Implement a class-based middleware registration system inspired by ASP.NET Core's IMiddleware pattern, adapted for AWS Lambda invocations:
Core Interface
namespace AwsLambda.Host.Abstractions;
/// <summary>
/// Defines middleware that can be added to the Lambda invocation pipeline.
/// </summary>
public interface ILambdaMiddleware
{
/// <summary>
/// Processes the Lambda invocation.
/// </summary>
/// <param name="context">The Lambda host context.</param>
/// <param name="next">The next middleware in the pipeline.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task InvokeAsync(ILambdaHostContext context, LambdaInvocationDelegate next);
}
Registration API
extension(ILambdaInvocationBuilder application)
{
/// <summary>
/// Registers middleware of type <typeparamref name="TMiddleware"/> in the pipeline.
/// The middleware will be resolved from the service provider on each invocation.
/// </summary>
public ILambdaInvocationBuilder UseMiddleware<TMiddleware>()
where TMiddleware : ILambdaMiddleware
{
// Implementation resolves TMiddleware from DI container
}
/// <summary>
/// Registers middleware of the specified type in the pipeline.
/// The middleware will be resolved from the service provider on each invocation.
/// </summary>
public ILambdaInvocationBuilder UseMiddleware(Type middlewareType)
{
// Implementation resolves middlewareType from DI container
}
}
Example Implementation
// Example middleware class with DI
public class LoggingMiddleware : ILambdaMiddleware
{
private readonly ILogger<LoggingMiddleware> _logger;
public LoggingMiddleware(ILogger<LoggingMiddleware> logger)
{
_logger = logger;
}
public async Task InvokeAsync(ILambdaHostContext context, LambdaInvocationDelegate next)
{
_logger.LogInformation("Invocation started");
try
{
await next(context);
_logger.LogInformation("Invocation completed successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Invocation failed");
throw;
}
}
}
// Registration
services.AddScoped<LoggingMiddleware>();
var app = new LambdaApplication(host);
app.UseMiddleware<LoggingMiddleware>();
Implementation Options
Option 1: Runtime Reflection (Simple)
- Resolve middleware from DI container at runtime
- Use reflection to call
InvokeAsync
- Pros: Simple implementation, no build-time complexity
- Cons: Performance overhead from reflection
Option 2: Source Generation (Optimal)
- Generate strongly-typed middleware adapters at compile-time
- Eliminate reflection overhead
- Provide compile-time validation
- Pros: Best performance, AOT-compatible, compile-time safety
- Cons: More complex implementation, requires incremental generator
Recommendation
Start with Option 1 for initial release, then add Option 2 as an optimization in a future release. This provides immediate value while allowing for future performance improvements.
Source Generation Considerations (Future Enhancement)
If implementing source generation:
// User code
[assembly: LambdaMiddleware(typeof(LoggingMiddleware))]
[assembly: LambdaMiddleware(typeof(ValidationMiddleware))]
// Generated code
partial class LambdaInvocationBuilderExtensions
{
public static ILambdaInvocationBuilder UseMiddleware<LoggingMiddleware>(
this ILambdaInvocationBuilder app)
{
return app.Use(next =>
{
return async context =>
{
var middleware = context.Services.GetRequiredService<LoggingMiddleware>();
await middleware.InvokeAsync(context, next);
};
});
}
}
Design Decisions
Lifecycle
- Scoped per invocation: Middleware instances are resolved from the DI container for each Lambda invocation
- This aligns with Lambda's execution model where each invocation gets a fresh scope
Service Resolution
- Middleware types should be registered in DI container by the user
- Use
GetRequiredService<T>() to fail fast if not registered
- Support both
Scoped and Transient lifetimes
Ordering
Error Handling
- If middleware type is not registered in DI, throw clear exception at invocation time
- Consider compile-time warnings with source generation
Benefits
- Testability: Middleware classes can be unit tested in isolation with mocked dependencies
- Maintainability: Complex middleware logic is organized in dedicated classes
- Dependency Injection: First-class support for injecting services into middleware
- Reusability: Middleware classes can be packaged and shared across projects
- Discoverability: Middleware implementations are easy to find and understand
- ASP.NET Core Familiarity: Developers familiar with ASP.NET Core will recognize the pattern
Example Usage Scenarios
Authentication Middleware
public class AuthenticationMiddleware : ILambdaMiddleware
{
private readonly IAuthService _authService;
public AuthenticationMiddleware(IAuthService authService)
{
_authService = authService;
}
public async Task InvokeAsync(ILambdaHostContext context, LambdaInvocationDelegate next)
{
var token = ExtractToken(context);
var principal = await _authService.ValidateTokenAsync(token);
// Add authenticated user to context
context.Features.Set<IPrincipal>(principal);
await next(context);
}
}
Validation Middleware
public class ValidationMiddleware<TEvent> : ILambdaMiddleware
{
private readonly IValidator<TEvent> _validator;
public ValidationMiddleware(IValidator<TEvent> validator)
{
_validator = validator;
}
public async Task InvokeAsync(ILambdaHostContext context, LambdaInvocationDelegate next)
{
var @event = context.Event.Get<TEvent>();
var result = await _validator.ValidateAsync(@event);
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
}
await next(context);
}
}
Related Issues
Breaking Changes
None - this is purely additive functionality that extends the existing middleware system.
Testing Considerations
- Unit tests for
ILambdaMiddleware interface implementation
- Unit tests for middleware registration extension methods
- Integration tests with DI container resolution
- Tests for middleware execution order
- Tests for error handling when middleware not registered in DI
- Performance benchmarks comparing reflection vs source generation approaches
- AOT compatibility tests if source generation is implemented
Documentation Requirements
- XML docs for
ILambdaMiddleware interface
- Usage guide with examples
- Migration guide for converting delegate-based middleware to class-based
- Performance considerations documentation
- Best practices for middleware design
Implementation Phases
Phase 1: Core Interface & Runtime Implementation
- Define
ILambdaMiddleware interface in AwsLambda.Host.Abstractions
- Implement
UseMiddleware<T>() extension method with runtime resolution
- Add unit tests
- Add documentation
Phase 2: Source Generation (Optional Future Enhancement)
- Create incremental source generator project
- Implement generator for middleware adapters
- Add compile-time diagnostics
- Performance benchmarks
- AOT compatibility validation
Type of Change
Acceptance Criteria
Summary
Add support for registering middleware from classes implementing an
ILambdaMiddlewareinterface, providing a cleaner, testable, and more maintainable alternative to inline delegate-based middleware. This feature may include source generation for enhanced performance and compile-time validation.Problem Statement
Currently, middleware can only be registered using inline delegates via
UseMiddleware()or as static extension methods. This approach has several limitations:Proposed Solution
Implement a class-based middleware registration system inspired by ASP.NET Core's
IMiddlewarepattern, adapted for AWS Lambda invocations:Core Interface
Registration API
Example Implementation
Implementation Options
Option 1: Runtime Reflection (Simple)
InvokeAsyncOption 2: Source Generation (Optimal)
Recommendation
Start with Option 1 for initial release, then add Option 2 as an optimization in a future release. This provides immediate value while allowing for future performance improvements.
Source Generation Considerations (Future Enhancement)
If implementing source generation:
Design Decisions
Lifecycle
Service Resolution
GetRequiredService<T>()to fail fast if not registeredScopedandTransientlifetimesOrdering
Error Handling
Benefits
Example Usage Scenarios
Authentication Middleware
Validation Middleware
Related Issues
Breaking Changes
None - this is purely additive functionality that extends the existing middleware system.
Testing Considerations
ILambdaMiddlewareinterface implementationDocumentation Requirements
ILambdaMiddlewareinterfaceImplementation Phases
Phase 1: Core Interface & Runtime Implementation
ILambdaMiddlewareinterface inAwsLambda.Host.AbstractionsUseMiddleware<T>()extension method with runtime resolutionPhase 2: Source Generation (Optional Future Enhancement)
Type of Change
Acceptance Criteria
ILambdaMiddlewareinterface defined in Abstractions packageUseMiddleware<T>()extension method implemented