Hona.VerticalSliceArchitecture.Template 2.0.0-rc4

This is a prerelease version of Hona.VerticalSliceArchitecture.Template.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet new install Hona.VerticalSliceArchitecture.Template::2.0.0-rc4                
This package contains a .NET Template Package you can call from the shell/command line.

alternate text is missing from this package README image alternate text is missing from this package README image alternate text is missing from this package README image alternate text is missing from this package README image alternate text is missing from this package README image GitHub Repo stars GitHub contributors GitHub last commit GitHub commit activity open issues

Spend less time over-engineering, and more time coding. The template has a focus on convenience, and developer confidence.

Want to see what a vertical slice looks like? Jump to the code snippet!

<p align="center"> <img src="docs/divider-primary.png" /> </p>

[!IMPORTANT] This template is undergoing a rebuild, ready for version 2! 🥳 See my experimental version 1 template here

Please wait patiently as this reaches the stable version, there's many important things to finish.

Please ⭐ the repository to show your support!

If you would like updates, feel free to 'Watch' the repo, that way you'll see the release in your GitHub home feed.

<p align="center"> <img src="docs/divider-primary.png" /> </p>

<h3 align="center"><strong>Getting started ⚡</strong></h3>

<p align="center"> <img src="docs/divider-primary.png" /> </p>

dotnet CLI

To install the template from NuGet.org run the following command:

dotnet new install Hona.VerticalSliceArchitecture.Template

Then create a new solution:

mkdir Sprout
cd Sprout

dotnet new hona-vsa

Finally, to update the template to the latest version run:

dotnet new update
GUI
dotnet new install Hona.VerticalSliceArchitecture.Template

then create:

<img src="https://github.com/user-attachments/assets/797575c2-1304-4501-b9d2-6b6863ace0f3" width="500" />

<h3 align="center"><strong>Features ✨</strong></h3>

<p align="center"> <img src="docs/divider-primary.png" /> </p>

A compelling example with the TikTacToe game! 🎮

var game = new Game(...);
game.MakeMove(0, 0, Tile.X);
game.MakeMove(0, 1, Tile.Y);

<p align="center"> <img src="docs/divider-secondary.png" /> </p>

Rich Domain (thank you DDD!)

  • with Vogen for Value-Objects
  • with FluentResults for errors as values instead of exceptions
  • For the Domain, start with an anemic Domain, then as use cases reuse logic, refactor into this more explicit Domain
[ValueObject<Guid>]
public readonly partial record struct GameId;

public class Game
{
    public GameId Id { get; init; } = GameId.From(Guid.NewGuid());

    ...

<p align="center"> <img src="docs/divider-secondary.png" /> </p>

Quick to write feature slices

  • Use cases follow CQRS using Mediator (source gen alternative of MediatR)
  • REPR pattern for the use cases
  • 1 File per use case, containing the endpoint mapping, request, response, handler & application logic
    • Endpoint is source generated
  • For use cases, start with 'just get it working' style code, then refactor into the Domain/Common code.
  • Mapster for source gen/explicit mapping, for example from Domain → Response/ViewModels

Features/MyThings/MyQuery.cs

internal sealed record MyRequest(string Text);
internal sealed record MyResponse(int Result);

internal sealed class MyQuery(AppDbContext db)
    : Endpoint<MyRequest, Results<Ok<GameResponse>, BadRequest>>
{
    public override void Configure()
    {
        Get("/my/{Text}");
    }

    public override async Task HandleAsync(
        MyRequest request,
        CancellationToken cancellationToken
    )
    {
        var thing = await db.Things.SingleAsync(x => x.Text == Text, cancellationToken);

        if (thing is null)
        {
            await SendResultAsync(TypedResults.BadRequest());
            return;
        }

        var output = new MyResponse(thing.Value);
        await SendResultAsync(TypedResults.Ok(output));
    }
}

<p align="center"> <img src="docs/divider-secondary.png" /> </p>

EF Core

  • Common:
    • EF Core, with fluent configuration
    • This sample shows simple config to map a rich entity to EF Core without needing a data model (choose how you'd do this for your project)

Common/EfCore/AppDbContext.cs

public class AppDbContext : DbContext
{
    public DbSet<MyEntity> MyEntities { get; set; } = default!;

    ...
}

Common/EfCore/Configuration/MyEntityConfiguration.cs

public class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity>
{
    public void Configure(EntityTypeBuilder<MyEntity> builder)
    {
        builder.HasKey(x => x.Id);

        ...
    }
}

<p align="center"> <img src="docs/divider-secondary.png" /> </p>

Architecture Tests via NuGet package

  • Pre configured VSA architecture tests, using NuGet (Hona.ArchitectureTests). The template has configured which parts of the codebase relate to which VSA concepts. 🚀
public class VerticalSliceArchitectureTests
{
    [Fact]
    public void VerticalSliceArchitecture()
    {
        Ensure.VerticalSliceArchitecture(x =>
        {
            x.Domain = new NamespacePart(SampleAppAssembly, ".Domain");
            ...
        }).Assert();
    }
}

<p align="center"> <img src="docs/divider-secondary.png" /> </p>

Cross Cutting Concerns

  • TODO:
    • Add Mediator FastEndpoints pipelines for cross cutting concerns on use cases, like logging, auth, validation (FluentValidation) etc (i.e. Common scoped to Use Cases)

Automated Testing

<p align="center"> <img src="docs/divider-tertiary.png" /> </p>

Domain - Unit Tested

Easy unit tests for the Domain layer

[Fact]
public void Game_MakeMove_BindsTile()
{
    // Arrange
    var game = new Game(GameId.From(Guid.NewGuid()), "Some Game");
    var tile = Tile.X;
    
    // Act
    game.MakeMove(0, 0, tile);
    
    // Assert
    game.Board.Value[0][0].Should().Be(tile);
}

<p align="center"> <img src="docs/divider-secondary.png" /> </p>

Application - Integration Tested

Easy integration tests for each Use Case or Command/Query

TODO: Test Containers, etc for integration testing the use cases. How does this tie into FastEndpoints now... 😄

TODO

<p align="center"> <img src="docs/divider-secondary.png" /> </p>

Code - Architecture Tested

The code is already architecture tested for VSA, but this is extensible, using Hona.ArchitectureTests

Full Code Snippet

To demostrate the template, here is a current whole vertical slice/use case!

// 👇🏻 Vogen for Strong IDs + 👇🏻 'GameId' field is hydrated from the route parameter
internal sealed record PlayTurnRequest(GameId GameId, int Row, int Column, Tile Player);

// 👇🏻 TypedResults for write once output as well as Swagger documentation
internal sealed class PlayTurnCommand(AppDbContext db)
    : Endpoint<PlayTurnRequest, Results<Ok<GameResponse>, NotFound>>
{
    // 👇🏻 FastEndpoints for a super easy Web API
    public override void Configure()
    {
        Post("/games/{GameId}/play-turn");
        Summary(x =>
        {
            x.Description = "Make a move in the game";
        });
        AllowAnonymous();
    }

    public override async Task HandleAsync(
        PlayTurnRequest request,
        CancellationToken cancellationToken
    )
    {
        // 👇🏻 EF Core without crazy abstractions over the abstraction
        var game = await db.FindAsync<Game>(request.GameId);

        if (game is null)
        {
            await SendResultAsync(TypedResults.NotFound());
            return;
        }

        // 👇🏻 Rich Domain for high value/shared logic
        game.MakeMove(request.Row, request.Column, request.Player);
        await db.SaveChangesAsync(cancellationToken);

        // 👇🏻 Mapster to easily get a view model
        var output = game.Adapt<GameResponse>();
        await SendResultAsync(TypedResults.Ok(output));
    }
}

If you read it this far, why not give it a star? 😉

<p align="center"> <img src="docs/divider-tertiary.png" /> </p>

<p align="center"> <img src="https://repobeats.axiom.co/api/embed/5491ee3c19266af4f681dcf016c299dfc2973b5f.svg" alt="Repobeats analytics image" /> </p>

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0-rc5 75 7/30/2024
2.0.0-rc4 46 7/30/2024
2.0.0-rc3 34 7/30/2024
2.0.0-rc2 43 7/30/2024
2.0.0-rc1 49 7/30/2024
2.0.0-beta1 49 7/30/2024
2.0.0-alpha1 81 7/22/2024
0.8.0 564 3/23/2024
0.7.4 225 3/20/2024
0.7.3 174 3/20/2024
0.7.2 161 3/20/2024
0.7.1 122 3/20/2024
0.7.0 182 3/20/2024
0.6.0 285 3/14/2024
0.5.3 2,483 12/22/2023
0.5.2 213 12/22/2023
0.5.1 189 12/22/2023
0.5.0 183 12/22/2023
0.4.0 225 12/21/2023
0.3.0 1,355 11/15/2023
0.2.1 982 10/22/2023
0.2.0 328 10/22/2023
0.1.4 310 9/20/2023
0.1.3 285 9/20/2023
0.1.2 316 9/20/2023
0.1.1 291 9/20/2023

Early preview of my v2 VSA template.