A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/kedzior-io/astro-architecture below:

kedzior-io/astro-architecture: Astro Architecture Solution Template .NET9

Astro Architecture Solution Template for .NET 9

This solution template provides a streamlined and modern way to build ASP.NET Core Web APIs by combining CQRS (Command Query Responsibility Segregation), DDD (Domain-Driven Design), and Vertical Slice Architecture. It’s designed to be simple, consistent, and testable while minimizing setup overhead.

Powered by MinimalCQRS, this template delivers a truly zero-setup experience - literally requiring just this single line:

builder.Services.AddMinimalCqrs();

Some people think CQRS and Vertical Slice Architecture are too complicated and take forever to set up. This template proves that's not true. Here's what it brings:

  1. The project creates SQLite database on the runtime, go to AstroArchitecture.Core.Constants.ConnectionStrings and set the desired location of your SQLite. Use location the runtime can write to.

  2. Once launched it will pop up swagger page: /swagger/index.html

  1. Create a GET endpoint
app.MapGetHandler<ListOrders.Query, ListOrders.Response>("/orders.list");
  1. Create a handler πŸ‘‡
public static class ListOrders
{
    public sealed record Query() : IQuery<IHandlerResponse<Response>>;

    public sealed class Response(IReadOnlyCollection<Order> orders)
    {
        public IReadOnlyCollection<OrderListModel> Orders { get; private set; } = orders.Select(OrderListModel.Create).ToList();
    }

    public sealed class Handler(IHandlerContext context) : QueryHandler<Query, Response>(context)
    {
        public override async Task<IHandlerResponse<Response>> ExecuteAsync(Query query, CancellationToken ct)
        {
            var orders = await DbContext.Orders
                .AsNoTracking()
                .Include(x => x.Customer)
                .Include(x => x.Items)
                .ToListAsync(ct);

            return Success(new Response(orders));
        }
    }
}

... and that's it! πŸ™Œ

  1. Create a POST endpoint πŸ‘‡
app.MapPostHandler<CreateProduct.Command, CreateProduct.Response>("/products.create");
  1. Create a handler πŸ‘‡
public static class CreateProduct
{
    public sealed record Command(string Name, decimal Price, int Stock) : ICommand<IHandlerResponse<Response>>;
    public sealed record Response(int ProductId);

    public sealed class CommandValidator : Validator<Command>
    {
        public CommandValidator()
        {
            RuleFor(x => x.Name)
                .NotNull()
                .NotEmpty();

            RuleFor(x => x.Price)
                .NotNull()
                .NotEmpty();

            RuleFor(x => x.Stock)
                .NotNull()
                .NotEmpty();
        }
    }

    public sealed class Handler(IHandlerContext context) : CommandHandler<Command, Response>(context)
    {
        public override async Task<IHandlerResponse<Response>> ExecuteAsync(Command command, CancellationToken ct)
        {
            var product = new Product(command.Name, command.Price, command.Stock);
            await DbContext.Products.AddAsync(product, ct);
            await DbContext.SaveChangesAsync(ct);

            return Success(new Response(product.Id));
        }
    }
}

... and that's it! πŸ™Œ

  1. Unit test your handler πŸ‘‡
public class CreateProductTests
{
    private static CreateProduct.Handler GetHandler(ApplicationDbContext dbContext)
    {
        return new CreateProduct.Handler(HandlerContextFactory.GetHandlerContext(dbContext));
    }

    private static void SetTestData(ApplicationDbContext dc)
    {
        var products = new List<Product> {
            new("Cyberpunk 2077", 24.2m, 100),
            new("Dune: Spice Wars", 19.99m, 100),
            new("Starcraft 2: ", 0.99m, 100),
        };

        dc.Products.AddRange(products);
        dc.SaveChanges();
    }

    [Fact]
    public async Task Create_Product_ReturnProductId()
    {
        var dc = InMemoryDbContextFactory.Create();
        SetTestData(dc);

        var productName = "Doom: Eternal";

        var command = new CreateProduct.Command(productName, 6.66m, 666);

        var expectedName = productName;
        var expectedCount = 4;

        var response = await GetHandler(dc).ExecuteAsync(command, default);

        Assert.NotNull(response?.Payload?.ProductId);

        var result = dc.Products.SingleOrDefault(o => o.Id == response.Payload.ProductId);
        var actualCount = dc.Products.Count();

        Assert.NotNull(result);
        Assert.Equal(expectedName, result.Name);
        Assert.Equal(expectedCount, expectedCount);
    }

    [Fact]
    public void Create_Product_ReturnNameTooShort()
    {
        var dc = InMemoryDbContextFactory.Create();
        SetTestData(dc);

        var command = new CreateProduct.Command("", 6.66m, 666);

        Assert.ThrowsAsync<ArgumentException>(async () => await GetHandler(dc).ExecuteAsync(command, default));
    }
}

Check samples here: MinimalCQRS

More info: Building .NET 9 & .NET 8 APIs with Zero-Setup CQRS and Vertical Slice Architecture

Here are things I want to add to this repo:

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Questions, feature requests

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4