CommandQuery.GoogleCloudFunctions
2.0.0
See the version list below for details.
dotnet add package CommandQuery.GoogleCloudFunctions --version 2.0.0
NuGet\Install-Package CommandQuery.GoogleCloudFunctions -Version 2.0.0
<PackageReference Include="CommandQuery.GoogleCloudFunctions" Version="2.0.0" />
paket add CommandQuery.GoogleCloudFunctions --version 2.0.0
#r "nuget: CommandQuery.GoogleCloudFunctions, 2.0.0"
// Install CommandQuery.GoogleCloudFunctions as a Cake Addin #addin nuget:?package=CommandQuery.GoogleCloudFunctions&version=2.0.0 // Install CommandQuery.GoogleCloudFunctions as a Cake Tool #tool nuget:?package=CommandQuery.GoogleCloudFunctions&version=2.0.0
CommandQuery.GoogleCloudFunctions ⚡
Command Query Separation for Google Cloud Functions
- Provides generic function support for commands and queries with HTTP functions
- Enables APIs based on HTTP
POST
andGET
Get Started
- Install Google.Cloud.Functions.Templates
- Create a new gcf-http project
- Install the
CommandQuery.GoogleCloudFunctions
package from NuGetPM>
Install-Package CommandQuery.GoogleCloudFunctions
- 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 services in
Startup.cs
Commands
using System.Threading.Tasks;
using CommandQuery.GoogleCloudFunctions;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace CommandQuery.Sample.GoogleCloudFunctions
{
[FunctionsStartup(typeof(Startup))]
public class Command : IHttpFunction
{
private readonly ILogger _logger;
private readonly ICommandFunction _commandFunction;
public Command(ILogger<Command> logger, ICommandFunction commandFunction)
{
_logger = logger;
_commandFunction = commandFunction;
}
public async Task HandleAsync(HttpContext context)
{
var commandName = context.Request.Path.Value.Substring("/api/command/".Length);
await _commandFunction.HandleAsync(commandName, context, _logger, context.RequestAborted);
}
}
}
- 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.Threading.Tasks;
using CommandQuery.GoogleCloudFunctions;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace CommandQuery.Sample.GoogleCloudFunctions
{
[FunctionsStartup(typeof(Startup))]
public class Query : IHttpFunction
{
private readonly ILogger _logger;
private readonly IQueryFunction _queryFunction;
public Query(ILogger<Query> logger, IQueryFunction queryFunction)
{
_logger = logger;
_queryFunction = queryFunction;
}
public async Task HandleAsync(HttpContext context)
{
var queryName = context.Request.Path.Value.Substring("/api/query/".Length);
await _queryFunction.HandleAsync(queryName, context, _logger, context.RequestAborted);
}
}
}
- 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 System.Text.Json;
using CommandQuery.GoogleCloudFunctions;
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 Google.Cloud.Functions.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace CommandQuery.Sample.GoogleCloudFunctions
{
public class Startup : FunctionsStartup
{
public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
services
//.AddSingleton(new JsonSerializerOptions(JsonSerializerDefaults.Web))
// Add commands and queries
.AddCommandFunction(typeof(FooCommandHandler).Assembly, typeof(FooCommand).Assembly)
.AddQueryFunction(typeof(BarQueryHandler).Assembly, typeof(BarQuery).Assembly)
// Add handler dependencies
.AddTransient<IDateTimeProxy, DateTimeProxy>()
.AddTransient<ICultureService, CultureService>();
public override void Configure(WebHostBuilderContext context, IApplicationBuilder app)
{
// Validation
app.ApplicationServices.GetService<ICommandProcessor>().AssertConfigurationIsValid();
app.ApplicationServices.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(Startup).Assembly
as a single argument.
Testing
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using CommandQuery.GoogleCloudFunctions;
using CommandQuery.Sample.Contracts.Queries;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
namespace CommandQuery.Sample.GoogleCloudFunctions.Tests
{
public class QueryTests
{
public class when_using_the_real_function_via_Post
{
[SetUp]
public void SetUp()
{
var serviceCollection = new ServiceCollection();
new Startup().ConfigureServices(null, serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
Subject = new Query(null, serviceProvider.GetService<IQueryFunction>());
}
[Test]
public async Task should_work()
{
var context = GetHttpContext("BarQuery", "POST", content: "{ \"Id\": 1 }");
await Subject.HandleAsync(context);
var value = await context.Response.AsAsync<Bar>();
value.Id.Should().Be(1);
value.Value.Should().NotBeEmpty();
}
[Test]
public async Task should_handle_errors()
{
var context = GetHttpContext("FailQuery", "POST", content: "{ \"Id\": 1 }");
await Subject.HandleAsync(context);
await context.Response.ShouldBeErrorAsync("The query type 'FailQuery' could not be found");
}
Query Subject;
}
public class when_using_the_real_function_via_Get
{
[SetUp]
public void SetUp()
{
var serviceCollection = new ServiceCollection();
new Startup().ConfigureServices(null, serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
Subject = new Query(null, serviceProvider.GetService<IQueryFunction>());
}
[Test]
public async Task should_work()
{
var context = GetHttpContext("BarQuery", "GET", query: new Dictionary<string, string> { { "Id", "1" } });
await Subject.HandleAsync(context);
var value = await context.Response.AsAsync<Bar>();
value.Id.Should().Be(1);
value.Value.Should().NotBeEmpty();
}
[Test]
public async Task should_handle_errors()
{
var context = GetHttpContext("FailQuery", "GET", query: new Dictionary<string, string> { { "Id", "1" } });
await Subject.HandleAsync(context);
await context.Response.ShouldBeErrorAsync("The query type 'FailQuery' could not be found");
}
Query Subject;
}
static HttpContext GetHttpContext(string queryName, string method, string content = null, Dictionary<string, string> query = null)
{
var context = new DefaultHttpContext();
context.Request.Path = new PathString("/api/query/" + queryName);
context.Request.Method = method;
if (content != null)
{
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(content));
}
if (query != null)
{
context.Request.QueryString = new QueryString(QueryHelpers.AddQueryString("", query));
}
context.Response.Body = new MemoryStream();
return context;
}
}
}
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
- CommandQuery (>= 2.0.0)
- CommandQuery.SystemTextJson (>= 2.0.0)
- Google.Cloud.Functions.Framework (>= 1.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.GoogleCloudFunctions:
Repository | Stars |
---|---|
hlaueriksson/CommandQuery
Command Query Separation for 🌐ASP.NET Core ⚡AWS Lambda ⚡Azure Functions ⚡Google Cloud Functions
|
- New project ✨
- Target framework netcoreapp3.1 🎯
- Uses System.Text.Json 📜