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
Top comments (0)