# Architecture overview

> Vertical slice architecture, the .NET projects and the patterns that divide them, the React SPA, and how a feature is added across both sides.

## Vertical slices, in one minute

Most codebases are organised by layer: a controllers folder, a services folder, a repositories
folder, a models folder. A single feature is then smeared across all of them, so changing one
behaviour means editing four places and reading code that serves a hundred unrelated features.

Vertical slice architecture turns that ninety degrees. Code is organised by feature, not by
technical layer. Everything one use case needs (its request, its logic, its validation, its result)
lives together in one folder. The slice you reason about as a human is the slice in the code, so a
feature is something you can read, change or delete in one place.

The benefits are practical: less coupling between unrelated features, changes that stay local, an
obvious place for every piece of code, and a shape a new engineer (or an AI assistant) can learn
once and apply everywhere. For the deeper rationale, Jimmy Bogard's
[Vertical Slice Architecture](https://www.jimmybogard.com/vertical-slice-architecture/) is the
canonical write-up.

Slicekit applies this shape on both sides of the wire: the .NET API is slice-per-feature, and so is
the React SPA.

## The API, project by project

The API is **vertical slice + domain-driven design + event-driven CQRS** on EF Core and PostgreSQL.
It is deliberately *not* a clean-architecture onion of many layered projects, and there is *no*
repository abstraction: the `AppDbContext` is the unit of work and the repository.

The whole API is two source projects, plus four test projects that guard them.

- **`Slicekit.Api`** is the host. It owns HTTP: endpoints, middleware, the composition root, OpenAPI.
  Endpoints are thin adapters that map a route, its policies (authorization, validation, rate
  limiting, CSRF) and its status codes.
- **`Slicekit.Core`** holds everything else: the feature slices under `Features/`, the domain under
  `Domain/`, persistence under `Persistence/`, and the messaging configuration. It has no dependency
  on the web host.

That one-way dependency is the point. Because handlers are invoked over
[Wolverine](https://wolverinefx.net)'s message bus rather than called from controllers, the API is
just one possible host. A CLI tool, a background worker or a scheduled job can dispatch the exact
same commands without touching any HTTP code.

Two domain rules are worth stating up front, because they keep the model honest:

- **Aggregates raise events.** Business rules live in aggregates, which enforce their own invariants
  and *raise* a domain event to record what happened. Raising is a fact inside the domain; it sends
  nothing.
- **Dispatchers publish events.** After the change commits, raised events are *published* to
  interested handlers and to the transactional outbox.

How commands, queries, events and the outbox fit together is its own page:
[CQRS and domain events](/docs/cqrs-and-events).

At runtime the picture is simple: the SPA talks to the API through one typed client, and the API
owns persistence, messaging and telemetry.

## The frontend

The SPA mirrors the API's slice-per-feature shape. Each feature is a folder under `features/` that
owns its typed client calls, its TanStack Query hooks, its localized Zod schemas and its components.
Shared primitives (the UI kit, the API client, theme handling) live in `components/` and `lib/`;
anything feature-specific stays inside the feature.

It is a Vite + React 19 app in strict TypeScript, with **TanStack Router** for type-safe routing,
**TanStack Query** for server state, and **shadcn/ui** on Tailwind. Every request goes through one
typed client that handles cookie sessions, the CSRF header and a silent refresh-and-retry on 401.
See the [frontend overview](/docs/frontend-overview) and [the typed API client](/docs/api-client).

## Adding a feature, end to end

Because both sides share the slice shape, one feature is one slice through the whole system. The path
is always the same:

1. **Write the slice in `Slicekit.Core`.** A new folder under `Features/` with the command (a plain
   record), the handler (the logic, returning a `Result`), validation, and the result type. The
   handler loads an aggregate, calls a domain method, and the aggregate raises its event.
2. **Map a thin endpoint in `Slicekit.Api`.** Declare the route, its permission, rate limit, validation
   and CSRF as policy, then hand the command to the Wolverine bus. No logic here.
3. **Mirror the slice on the frontend.** A folder under `features/` with the typed `api.ts` call, the
   `hooks.ts` query and mutation, localized `schemas.ts`, and the components that render it.
4. **Lean on the guardrails.** Architecture tests fail the build if a slice reaches across a feature
   boundary, and feature tests run against a real PostgreSQL via Testcontainers.

The full step-by-step lives in the two recipes: [adding a vertical slice](/docs/vertical-slices) on
the API, and the matching frontend recipe in the docs.

## Proven, not experimental

Slicekit runs on mainstream technology with years (some decades) behind it: PostgreSQL, Redis,
RabbitMQ, relational data, CQRS, domain-driven design, REST and JWT, on the current stable releases
of .NET and React. The patterns are battle-tested and the dependencies are ones you can hire for and
reason about. There is no exotic runtime and nothing bleeding-edge that breaks on the next upgrade:
modern where it helps you, boring where it protects you.

### What is deliberately absent

- **No clean-architecture onion.** Two source projects, not a stack of layered ones.
- **No repository pattern.** The `AppDbContext` is the unit of work and the repository.
- **No data-migration shims.** Slicekit is a template with no existing data to bridge.
- **No bespoke runtime.** Everything is standard .NET, EF Core and Wolverine.
