# Adding a vertical slice

> A step-by-step recipe for adding a new feature, from the command in Slicekit.Core to the thin HTTP endpoint in Slicekit.Api.

## The anatomy of a slice

A feature lives in **two deliberately separate projects**. The slice itself, the command, its
handler, validation and results, sits in `Slicekit.Core` under `Features/`. The HTTP endpoint is a
thin adapter in `Slicekit.Api` under `Endpoints/v1/`:

```
api/src/Slicekit.Core/
  Features/
    Projects/
      CreateProject/
        Command.cs        // the request, a plain record
        Handler.cs        // the business logic
        Validator.cs      // FluentValidation rules
        Result.cs         // what the handler returns
  Domain/
    Project.cs            // the aggregate

api/src/Slicekit.Api/
  Endpoints/v1/Projects/
    CreateProjectEndpoint.cs   // route, policies, status codes
```

The split is the point: `Slicekit.Core` has no dependency on the web host. Handlers are invoked over
[Wolverine](https://wolverinefx.net)'s message bus, so the API is just one host. A CLI tool, a
background worker or a scheduled job can dispatch the exact same commands without touching any
HTTP code.

## 1. Define the command

A command is a plain record in `Command.cs`:

```csharp
namespace Slicekit.Core.Features.Projects.CreateProject;

public sealed record CreateProjectCommand(Guid UserId, string Name);
```

## 2. Write the handler

The handler lives next to the command in `Handler.cs` and is discovered by Wolverine, with no
registration required. It returns a `Result` so failures map onto the shared error taxonomy:

```csharp
public sealed class CreateProjectCommandHandler(AppDbContext db)
{
    public async Task<Result> HandleAsync(
        CreateProjectCommand command,
        CancellationToken ct = default)
    {
        var project = Project.Create(command.UserId, command.Name);   // aggregate raises ProjectCreated
        db.Projects.Add(project);
        await db.SaveChangesAsync(ct);
        return new CreateProjectResult(project.Id);
    }
}
```

## 3. Raise events from the aggregate

The aggregate owns its invariants and records what happened by **raising** an event:

```csharp
public class Project : AggregateRoot
{
    public static Project Create(Guid ownerId, string name)
    {
        var project = new Project { Id = Guid.CreateVersion7(), OwnerId = ownerId, Name = name };
        project.Raise(new ProjectCreated(project.Id, name));
        return project;
    }
}
```

Raised events are published after the change is saved, to any interested handlers and to the
transactional outbox. See [CQRS and events](/docs/cqrs-and-events).

## 4. Map the endpoint

The endpoint, in `Slicekit.Api`, translates HTTP into the command and the result into a response.
Authorization, validation, rate limiting and CSRF are declared as route policy:

```csharp
internal sealed class CreateProjectEndpoint : IEndpoint
{
    public static void Map(IEndpointRouteBuilder routes) =>
        routes.Projects().MapPost("/", HandleAsync)
            .RequirePermission(Allow.ProjectCreate)
            .RequireRateLimiting(RateLimitPolicies.Default)
            .AddEndpointFilter<ValidationEndpointFilter>()
            .RequireCsrf();

    private static async Task<Results<Created, ProblemHttpResult>> HandleAsync(
        Request request, ClaimsPrincipal principal, IMessageBus bus, CancellationToken ct)
    {
        var result = await bus.InvokeAsync<Result>(
            new CreateProjectCommand(principal.TryGetUserId() ?? throw new UnauthorizedAccessException(), request.Name), ct);

        if (!result.IsSuccess) return result.Error.ToProblem();
        return TypedResults.Created($"/projects/{result.Value.Id}", new Response(result.Value.Id));
    }
}
```

## 5. Test the slice

Unit-test the handler and the aggregate with the fast suite; cover the endpoint with an integration
test backed by Testcontainers. Architecture tests enforce that slices do not reach across feature
boundaries.

```sh
dotnet test api/tests/Slicekit.Unit.Tests api/tests/Slicekit.Architecture.Tests --nologo
```

## Conventions to keep

- **One feature, one folder.** Do not scatter a feature across shared layers.
- **Endpoints stay thin.** Routing, policies and status codes only; logic belongs in the handler.
- **Raise, do not publish, from aggregates.** Dispatchers publish; aggregates raise.
- **No repository interfaces.** Use `AppDbContext` directly.
