Easiest way to build the fastest REST API in C# and .NET 7 using CQRS
Artur Kedzior
Posted on April 25, 2023
Sometime ago I stumbled upon this amazing library built by amazing people: https://fast-endpoints.com
It was also featured on this platform https://dev.to/djnitehawk/building-rest-apis-in-net-6-the-easy-way-3h0d (written by the author).
I gave it a go and I was impressed how easy and fast it was to set it all up. Since I'm not a big fan of REPR pattern almost all my projects are using CQRS pattern with a help of MediatR ](https://github.com/jbogard/MediatR) I immediately started going over something similar that Fast Endpoints offer which is a command bus.
So I put the project together with certain goals:
- Keep everything in command/query handlers
- Keep API as thin as possible
- Execute handlers within Azure Functions (keeping them thin as well)
- Make handlers easily unit testable
I put all this up on Github FastArchitecture š„
You can see it in action on a real-world project Salarioo.com š„
The API endpoint ended up being a single line:
public class GetOrdersEndpoint : ApiEndpoint<GetOrders.Query, GetOrders.Response>
{
public override void Configure()
{
Get("orders/list");
AllowAnonymous();
ResponseCache(60);
}
public override async Task HandleAsync(GetOrders.Query query, CancellationToken ct) => await SendAsync(query, ct);
}
The Query handler in a self contained class that contains:
- Query definition (the input)
- Query response (the output)
- Handler (stuff that happens within execution)
public static class GetOrders
{
public sealed class Query : IQuery<IHandlerResponse<Response>>
{
}
public sealed class Response
{
public IReadOnlyCollection<OrderListModel> Orders { get; private set; }
public Response(IReadOnlyCollection<Domain.Order> orders)
{
Orders = orders.Select(OrderListModel.Create).ToList();
}
}
public sealed class Handler : QueryHandler<Query, Response>
{
public Handler(IHandlerContext context) : base(context)
{
}
public override async Task<IHandlerResponse<Response>> ExecuteAsync(Query query, CancellationToken ct)
{
var orders = await DbContext
.Orders
.ToListAsync(ct);
return Success(new Response(orders));
}
}
}
Here is an example of Command handler with built-in Fluent Validation and fire and forget style:
public static class ConfirmAllOrders
{
public sealed class Command : ICommand
{
public string Name { get; set; } = "";
}
public sealed class MyValidator : Validator<Command>
{
public MyValidator()
{
RuleFor(x => x.Name)
.MinimumLength(5)
.WithMessage("Order name is too short!");
}
}
public sealed class Handler : Abstractions.CommandHandler<Command>
{
public Handler(IHandlerContext context) : base(context)
{
}
public override async Task<IHandlerResponse> ExecuteAsync(Command command, CancellationToken ct)
{
var orders = await DbContext
.Orders
.ToListAsync(ct);
orders.ForEach(x => x.SetConfrimed());
await DbContext.SaveChangesAsync(ct);
return Success();
}
}
}
Handlers allow 3 possible responses:
- Response without content:
return Success()
- Response with some content:
return Success(responseObj)
- An error*:
return Error("I felt a great disturbance in the Force, as if millions of voices suddenly cried out in terror and were suddenly silenced. I fear something terrible has happened.")
*soon to be implemented
Here is an example of running it from Azure Functions:
public class ConfirmAllOrdersFunction : FunctionBase<ConfirmAllOrdersFunction>
{
public ConfirmAllOrdersFunction(ILogger logger) : base(logger)
{
}
[Function(nameof(ConfirmAllOrdersFunction))]
public async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
{
var command = new ConfirmAllOrders.Command();
await ExecuteAsync<ConfirmAllOrders.Command>(command, req.FunctionContext);
}
}
š„ Interested?
š„ Checkout the complete source code on Github FastArchitecture
Posted on April 25, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.