CommandQuery.AWSLambda
4.0.0
dotnet add package CommandQuery.AWSLambda --version 4.0.0
NuGet\Install-Package CommandQuery.AWSLambda -Version 4.0.0
<PackageReference Include="CommandQuery.AWSLambda" Version="4.0.0" />
paket add CommandQuery.AWSLambda --version 4.0.0
#r "nuget: CommandQuery.AWSLambda, 4.0.0"
// Install CommandQuery.AWSLambda as a Cake Addin #addin nuget:?package=CommandQuery.AWSLambda&version=4.0.0 // Install CommandQuery.AWSLambda as a Cake Tool #tool nuget:?package=CommandQuery.AWSLambda&version=4.0.0
CommandQuery.AWSLambda ⚡
Command Query Separation for AWS Lambda
- Provides generic function support for commands and queries with Amazon API Gateway
- Enables APIs based on HTTP
POST
andGET
Get Started
- Install AWS Toolkit for Visual Studio
- Create a new AWS Serverless Application (.NET Core) project
- Install the
CommandQuery.AWSLambda
package from NuGetPM>
Install-Package CommandQuery.AWSLambda
- Create functions
- Preferably named
Command
andQuery
- Preferably named
- Create commands and command handlers
- Implement
ICommand
andICommandHandler<in TCommand>
- Or
ICommand<TResult>
andICommandHandler<in TCommand, TResult>
- Implement
- Create queries and query handlers
- Implement
IQuery<TResult>
andIQueryHandler<in TQuery, TResult>
- Implement
- Configure the serverless template
Choose:
- AWS Serverless Application (.NET Core - C#)
Choose:
- Empty Serverless Application
Commands
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using CommandQuery.AWSLambda;
namespace CommandQuery.Sample.AWSLambda;
public class Command(ICommandFunction commandFunction)
{
[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole", MemorySize = 256, Timeout = 30)]
[RestApi(LambdaHttpMethod.Post, "/command/{commandName}")]
public async Task<APIGatewayProxyResponse> Post(
APIGatewayProxyRequest request,
ILambdaContext context,
string commandName) =>
await commandFunction.HandleAsync(commandName, request, context.Logger);
}
- The function is requested via HTTP
POST
with the Content-Typeapplication/json
in the header. - The name of the command is the slug of the URL.
- The command itself is provided as JSON in the body.
- If the command succeeds; the response is empty with the HTTP status code
200
. - If the command fails; the response is an error message with the HTTP status code
400
or500
.
Commands with result:
- If the command succeeds; the response is the result as JSON with the HTTP status code
200
.
Queries
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using CommandQuery.AWSLambda;
namespace CommandQuery.Sample.AWSLambda
{
public class Query(IQueryFunction queryFunction)
{
[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole", MemorySize = 256, Timeout = 30)]
[RestApi(LambdaHttpMethod.Get, "/query/{queryName}")]
public async Task<APIGatewayProxyResponse> Get(
APIGatewayProxyRequest request,
ILambdaContext context,
string queryName) =>
await queryFunction.HandleAsync(queryName, request, context.Logger);
[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole", MemorySize = 256, Timeout = 30)]
[RestApi(LambdaHttpMethod.Post, "/query/{queryName}")]
public async Task<APIGatewayProxyResponse> Post(
APIGatewayProxyRequest request,
ILambdaContext context,
string queryName) =>
await queryFunction.HandleAsync(queryName, request, context.Logger);
}
}
- The function is requested via:
- HTTP
POST
with the Content-Typeapplication/json
in the header and the query itself as JSON in the body - HTTP
GET
and the query itself as query string parameters in the URL
- HTTP
- The name of the query is the slug of the URL.
- If the query succeeds; the response is the result as JSON with the HTTP status code
200
. - If the query fails; the response is an error message with the HTTP status code
400
or500
.
Configuration
Configuration in Startup.cs
:
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Core;
using CommandQuery.AWSLambda;
using CommandQuery.Sample.Contracts.Commands;
using CommandQuery.Sample.Contracts.Queries;
using CommandQuery.Sample.Handlers;
using CommandQuery.Sample.Handlers.Commands;
using CommandQuery.Sample.Handlers.Queries;
using Microsoft.Extensions.DependencyInjection;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace CommandQuery.Sample.AWSLambda;
[LambdaStartup]
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//services.AddSingleton(new JsonSerializerOptions(JsonSerializerDefaults.Web));
// Add commands and queries
services.AddCommandFunction(typeof(FooCommandHandler).Assembly, typeof(FooCommand).Assembly);
services.AddQueryFunction(typeof(BarQueryHandler).Assembly, typeof(BarQuery).Assembly);
// Add handler dependencies
services.AddTransient<IDateTimeProxy, DateTimeProxy>();
services.AddTransient<ICultureService, CultureService>();
// Validation
var serviceProvider = services.BuildServiceProvider();
serviceProvider.GetService<ICommandProcessor>()!.AssertConfigurationIsValid();
serviceProvider.GetService<IQueryProcessor>()!.AssertConfigurationIsValid();
}
}
The extension methods AddCommandFunction
and AddQueryFunction
will add functions and all command/query handlers in the given assemblies to the IoC container.
You can pass in a params
array of Assembly
arguments if your handlers are located in different projects.
If you only have one project you can use typeof(Program).Assembly
as a single argument.
Configuration in serverless.template
:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Description": "An AWS Serverless Application. This template is partially managed by Amazon.Lambda.Annotations (v1.5.0.0).",
"Resources": {
"CommandQuerySampleAWSLambdaCommandPostGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
"SyncedEvents": [
"RootPost"
],
"SyncedEventProperties": {
"RootPost": [
"Path",
"Method"
]
}
},
"Properties": {
"Architectures": [
"x86_64"
],
"Handler": "CommandQuery.Sample.AWSLambda::CommandQuery.Sample.AWSLambda.Command_Post_Generated::Post",
"Runtime": "dotnet8",
"CodeUri": ".",
"MemorySize": 256,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
"PackageType": "Zip",
"Events": {
"RootPost": {
"Type": "Api",
"Properties": {
"Path": "/command/{commandName}",
"Method": "POST"
}
}
}
}
},
"CommandQuerySampleAWSLambdaQueryGetGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
"SyncedEvents": [
"RootGet"
],
"SyncedEventProperties": {
"RootGet": [
"Path",
"Method"
]
}
},
"Properties": {
"Architectures": [
"x86_64"
],
"Handler": "CommandQuery.Sample.AWSLambda::CommandQuery.Sample.AWSLambda.Query_Get_Generated::Get",
"Runtime": "dotnet8",
"CodeUri": ".",
"MemorySize": 256,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
"PackageType": "Zip",
"Events": {
"RootGet": {
"Type": "Api",
"Properties": {
"Path": "/query/{queryName}",
"Method": "GET"
}
}
}
}
},
"CommandQuerySampleAWSLambdaQueryPostGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
"SyncedEvents": [
"RootPost"
],
"SyncedEventProperties": {
"RootPost": [
"Path",
"Method"
]
}
},
"Properties": {
"Architectures": [
"x86_64"
],
"Handler": "CommandQuery.Sample.AWSLambda::CommandQuery.Sample.AWSLambda.Query_Post_Generated::Post",
"Runtime": "dotnet8",
"CodeUri": ".",
"MemorySize": 256,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
"PackageType": "Zip",
"Events": {
"RootPost": {
"Type": "Api",
"Properties": {
"Path": "/query/{queryName}",
"Method": "POST"
}
}
}
}
}
},
"Outputs": {
"ApiURL": {
"Description": "API endpoint URL for Prod environment",
"Value": {
"Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}
Testing
You can test your lambdas with the Amazon.Lambda.TestUtilities package.
using System.Text.Json;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.TestUtilities;
using CommandQuery.AWSLambda;
using CommandQuery.Sample.Contracts.Commands;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
namespace CommandQuery.Sample.AWSLambda.Tests
{
public class CommandTests
{
[SetUp]
public void SetUp()
{
var serviceCollection = new ServiceCollection();
new Startup().ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
Subject = new Command(serviceProvider.GetRequiredService<ICommandFunction>());
Context = new TestLambdaContext();
}
[Test]
public async Task should_handle_command()
{
var response = await Subject.Post(GetRequest(new FooCommand { Value = "Foo" }), Context, "FooCommand");
response.StatusCode.Should().Be(200);
}
[Test]
public async Task should_handle_errors()
{
var response = await Subject.Post(GetRequest(new FooCommand()), Context, "FooCommand");
response.ShouldBeError("Value cannot be null or empty");
}
static APIGatewayProxyRequest GetRequest(object body) => new() { Body = JsonSerializer.Serialize(body) };
Command Subject = null!;
TestLambdaContext Context = null!;
}
}
Samples
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Amazon.Lambda.APIGatewayEvents (>= 2.7.0)
- Amazon.Lambda.Core (>= 2.2.0)
- CommandQuery (>= 4.0.0)
- CommandQuery.SystemTextJson (>= 4.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on CommandQuery.AWSLambda:
Repository | Stars |
---|---|
hlaueriksson/CommandQuery
Command Query Separation for 🌐ASP.NET Core ⚡AWS Lambda ⚡Azure Functions ⚡Google Cloud Functions
|
- Bump Amazon.Lambda.Core to 2.2.0
- Bump Amazon.Lambda.APIGatewayEvents to 2.7.0
- Make ILambdaLogger parameter required for HandleAsync
- Add support for APIGatewayHttpApiV2ProxyRequest