CommandQuery.AWSLambda
2.0.0
See the version list below for details.
dotnet add package CommandQuery.AWSLambda --version 2.0.0
NuGet\Install-Package CommandQuery.AWSLambda -Version 2.0.0
<PackageReference Include="CommandQuery.AWSLambda" Version="2.0.0" />
paket add CommandQuery.AWSLambda --version 2.0.0
#r "nuget: CommandQuery.AWSLambda, 2.0.0"
// Install CommandQuery.AWSLambda as a Cake Addin #addin nuget:?package=CommandQuery.AWSLambda&version=2.0.0 // Install CommandQuery.AWSLambda as a Cake Tool #tool nuget:?package=CommandQuery.AWSLambda&version=2.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:
- Empty Serverless Application
Commands
using System.Text.Json;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;
using CommandQuery.AWSLambda;
using CommandQuery.Sample.Contracts.Commands;
using CommandQuery.Sample.Handlers;
using CommandQuery.Sample.Handlers.Commands;
using Microsoft.Extensions.DependencyInjection;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace CommandQuery.Sample.AWSLambda
{
public class Command
{
private static readonly ICommandFunction _commandFunction = GetCommandFunction();
public async Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest request, ILambdaContext context)
{
return await _commandFunction.HandleAsync(request.PathParameters["commandName"], request, context.Logger);
}
private static ICommandFunction GetCommandFunction()
{
var services = new ServiceCollection();
//services.AddSingleton(new JsonSerializerOptions(JsonSerializerDefaults.Web));
services.AddCommandFunction(typeof(FooCommandHandler).Assembly, typeof(FooCommand).Assembly);
// Add handler dependencies
services.AddTransient<ICultureService, CultureService>();
var serviceProvider = services.BuildServiceProvider();
// Validation
serviceProvider.GetService<ICommandProcessor>().AssertConfigurationIsValid();
return serviceProvider.GetService<ICommandFunction>();
}
}
}
- 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 System.Text.Json;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;
using CommandQuery.AWSLambda;
using CommandQuery.Sample.Contracts.Queries;
using CommandQuery.Sample.Handlers;
using CommandQuery.Sample.Handlers.Queries;
using Microsoft.Extensions.DependencyInjection;
namespace CommandQuery.Sample.AWSLambda
{
public class Query
{
private static readonly IQueryFunction _queryFunction = GetQueryFunction();
public async Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest request, ILambdaContext context)
{
return await _queryFunction.HandleAsync(request.PathParameters["queryName"], request, context.Logger);
}
private static IQueryFunction GetQueryFunction()
{
var services = new ServiceCollection();
//services.AddSingleton(new JsonSerializerOptions(JsonSerializerDefaults.Web));
services.AddQueryFunction(typeof(BarQueryHandler).Assembly, typeof(BarQuery).Assembly);
// Add handler dependencies
services.AddTransient<IDateTimeProxy, DateTimeProxy>();
var serviceProvider = services.BuildServiceProvider();
// Validation
serviceProvider.GetService<IQueryProcessor>().AssertConfigurationIsValid();
return serviceProvider.GetService<IQueryFunction>();
}
}
}
- 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 serverless.template
:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Description": "An AWS Serverless Application.",
"Resources": {
"Command": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "CommandQuery.Sample.AWSLambda::CommandQuery.Sample.AWSLambda.Command::Handle",
"Runtime": "dotnetcore3.1",
"CodeUri": "",
"MemorySize": 256,
"Timeout": 30,
"Role": null,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
"Events": {
"PostResource": {
"Type": "Api",
"Properties": {
"Path": "/command/{commandName}",
"Method": "POST"
}
}
}
}
},
"Query": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "CommandQuery.Sample.AWSLambda::CommandQuery.Sample.AWSLambda.Query::Handle",
"Runtime": "dotnetcore3.1",
"CodeUri": "",
"MemorySize": 256,
"Timeout": 30,
"Role": null,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
"Events": {
"GetResource": {
"Type": "Api",
"Properties": {
"Path": "/query/{queryName}",
"Method": "GET"
}
},
"PostResource": {
"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
using System.Collections.Generic;
using System.Threading.Tasks;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using CommandQuery.Sample.Contracts.Queries;
using FluentAssertions;
using NUnit.Framework;
namespace CommandQuery.Sample.AWSLambda.Tests
{
public class QueryTests
{
public class when_using_the_real_function_via_Post
{
[SetUp]
public void SetUp()
{
Subject = new Query();
Request = GetRequest("POST", content: "{ \"Id\": 1 }");
Context = new FakeLambdaContext();
}
[Test]
public async Task should_work()
{
var result = await Subject.Handle(Request.QueryName("BarQuery"), Context);
var value = result.As<Bar>();
value.Id.Should().Be(1);
value.Value.Should().NotBeEmpty();
}
[Test]
public async Task should_handle_errors()
{
var result = await Subject.Handle(Request.QueryName("FailQuery"), Context);
result.ShouldBeError("The query type 'FailQuery' could not be found");
}
Query Subject;
APIGatewayProxyRequest Request;
ILambdaContext Context;
}
public class when_using_the_real_function_via_Get
{
[SetUp]
public void SetUp()
{
Subject = new Query();
Request = GetRequest("GET", query: new Dictionary<string, IList<string>> { { "Id", new List<string> { "1" } } });
Context = new FakeLambdaContext();
}
[Test]
public async Task should_work()
{
var result = await Subject.Handle(Request.QueryName("BarQuery"), Context);
var value = result.As<Bar>();
value.Id.Should().Be(1);
value.Value.Should().NotBeEmpty();
}
[Test]
public async Task should_handle_errors()
{
var result = await Subject.Handle(Request.QueryName("FailQuery"), Context);
result.ShouldBeError("The query type 'FailQuery' could not be found");
}
Query Subject;
APIGatewayProxyRequest Request;
ILambdaContext Context;
}
static APIGatewayProxyRequest GetRequest(string method, string content = null, Dictionary<string, IList<string>> query = null)
{
var request = new APIGatewayProxyRequest
{
HttpMethod = method,
Body = content,
MultiValueQueryStringParameters = query
};
return request;
}
}
}
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 | netcoreapp3.1 is compatible. |
-
.NETCoreApp 3.1
- Amazon.Lambda.APIGatewayEvents (>= 2.0.0)
- Amazon.Lambda.Core (>= 2.0.0)
- CommandQuery (>= 2.0.0)
- CommandQuery.SystemTextJson (>= 2.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
|
- Changed target framework to netcoreapp3.1 🎯
- Now uses System.Text.Json, instead of Newtonsoft.Json 📜
- Added the new abstractions ICommandFunction and IQueryFunction
- Added JsonSerializerOptions constructor parameter in CommandFunction and QueryFunction
- Renamed method to HandleAsync in CommandFunction and QueryFunction 💥
- Changed the ILambdaContext parameter to ILambdaLogger in HandleAsync methods in CommandFunction and QueryFunction 💥
- Added extension methods AddCommandFunction and AddQueryFunction on IServiceCollection
- Nested object graph queries via GET is now supported