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)