Skip to content

nadirbad/VerticalSliceArchitecture

Repository files navigation

Vertical Slice Architecture in .NET 10

A learning template demonstrating Vertical Slice Architecture with a healthcare appointment scheduling domain.

.NET 10 License GitHub stars Open in GitHub Codespaces

New to Vertical Slice Architecture? Read my Complete Guide to Vertical Slice Architecture or jump straight to the Quick Start Guide.

Give it a Star ⭐

If you find this template useful, please give it a star! It helps others discover this project.

What is Vertical Slice Architecture?

Instead of organizing code by technical layers (Controllers, Services, Repositories), Vertical Slice Architecture organizes code by features. Each feature contains everything it needs - endpoint, validation, business logic, and data access - all in one place.

Traditional Layered:              Vertical Slice:
├── Controllers/                  ├── Features/
│   └── AppointmentsController    │   ├── BookAppointment.cs      ← Everything in one file
├── Services/                     │   ├── CancelAppointment.cs
│   └── AppointmentService        │   ├── CompleteAppointment.cs
├── Repositories/                 │   └── GetAppointments.cs
│   └── AppointmentRepository     │
└── Models/                       └── Domain/
    └── Appointment                   └── Appointment.cs

Benefits:

  • Change one feature without touching others
  • No jumping between layers - everything is co-located
  • Easier to understand, test, and maintain
  • Features can evolve independently

Domain Overview

This template models a medical clinic appointment scheduling system where patients book appointments with doctors. The domain enforces real-world constraints: doctors cannot be double-booked, appointments must be scheduled in advance, and appointment lifecycle transitions are controlled (scheduled appointments can be completed or cancelled, but these are terminal states).

Features & Business Rules

Feature Endpoint Key Business Rules
Book Appointment POST /api/appointments No double-booking doctors, 15-min advance notice, 10min–8hr duration
Get Appointments GET /api/appointments Filter by patient, doctor, status, date range; paginated (max 100)
Get by ID GET /api/appointments/{id} Returns full appointment with patient/doctor details
Complete POST /api/appointments/{id}/complete Cannot complete cancelled appointments; idempotent
Cancel POST /api/appointments/{id}/cancel Cannot cancel completed appointments; requires reason; idempotent

Quick Start

# Clone and run
git clone https://github.com/nadirbad/VerticalSliceArchitecture.git
cd VerticalSliceArchitecture
dotnet run --project src/Api/Api.csproj

# Open Swagger UI
open http://localhost:5206

Dev Container / GitHub Codespaces

The fastest way to get a fully working environment with PostgreSQL — no local setup required.

GitHub Codespaces (browser-based): Click the badge above or go to Code > Codespaces > Create codespace. The environment will be ready with .NET 10, PostgreSQL, EF tools, and all VS Code extensions pre-configured.

VS Code Dev Container (local Docker):

  • Open in VS Code, then: Ctrl/Cmd+Shift+P → "Dev Containers: Reopen in Container"

The Dev Container automatically:

  • Installs .NET 10 SDK and EF Core tools
  • Starts PostgreSQL 16 with the database pre-created
  • Restores packages, builds, and applies migrations
  • Forwards ports for Swagger UI and database access

Project Structure

src/
├── Api/                          # ASP.NET Core entry point
│   └── Program.cs                # Minimal hosting setup
│
└── Application/                  # All features and domain logic
    ├── Scheduling/               # ← Feature slice
    │   ├── BookAppointment.cs    #   Command + Validator + Handler + Endpoint
    │   ├── CancelAppointment.cs
    │   ├── CompleteAppointment.cs
    │   ├── GetAppointments.cs
    │   └── GetAppointmentById.cs
    │
    ├── Domain/                   # Domain entities and events
    │   ├── Appointment.cs        #   Rich domain model with business logic
    │   ├── Patient.cs
    │   ├── Doctor.cs
    │   └── Events/
    │
    ├── Common/                   # Shared infrastructure
    │   ├── ValidationFilter.cs
    │   └── MinimalApiProblemHelper.cs
    │
    └── Infrastructure/           # Data access
        └── Persistence/
            └── ApplicationDbContext.cs

Feature Anatomy

Each feature file contains everything needed for that operation:

// BookAppointment.cs - Complete feature in one file

// 1. Endpoint Handler
public static class BookAppointmentEndpoint
{
    public static async Task<IResult> Handle(BookAppointmentCommand command, ISender mediator)
    {
        var result = await mediator.Send(command);
        return result.Match(
            success => Results.Created($"/api/appointments/{success.Id}", success),
            errors => MinimalApiProblemHelper.Problem(errors));
    }
}

// 2. Command (Request)
public record BookAppointmentCommand(
    Guid PatientId, Guid DoctorId,
    DateTimeOffset Start, DateTimeOffset End,
    string? Notes) : IRequest<ErrorOr<BookAppointmentResult>>;

// 3. Validator
internal sealed class BookAppointmentCommandValidator : AbstractValidator<BookAppointmentCommand>
{
    public BookAppointmentCommandValidator()
    {
        RuleFor(v => v.PatientId).NotEmpty();
        RuleFor(v => v.DoctorId).NotEmpty();
        RuleFor(v => v.Start).Must(BeInFuture).WithMessage("Must book at least 15 minutes in advance");
        // ... more rules
    }
}

// 4. Handler (Business Logic)
internal sealed class BookAppointmentCommandHandler : IRequestHandler<BookAppointmentCommand, ErrorOr<BookAppointmentResult>>
{
    public async Task<ErrorOr<BookAppointmentResult>> Handle(BookAppointmentCommand request, CancellationToken ct)
    {
        // Check for conflicts, create appointment, save to database
    }
}

Technologies

Technology Purpose
.NET 10 Minimal APIs Lightweight HTTP endpoints (LTS release)
MediatR Request/response pattern, pipeline behaviors
FluentValidation Declarative validation rules
ErrorOr Result pattern for error handling
Entity Framework Core 10 Data access with in-memory or PostgreSQL
xUnit + FluentAssertions Testing framework

Development Commands

# Build
dotnet build

# Run (Swagger at http://localhost:5206)
dotnet run --project src/Api/Api.csproj

# Run tests
dotnet test

# Format code
dotnet format

Sample Data

In development mode, the API automatically seeds sample patients and doctors:

Patients

ID Name Email
11111111-1111-1111-1111-111111111111 John Smith [email protected]
22222222-2222-2222-2222-222222222222 Jane Doe [email protected]
33333333-3333-3333-3333-333333333333 Bob Johnson [email protected]

Doctors

ID Name Specialty
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa Dr. Sarah Wilson Family Medicine
bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb Dr. Michael Chen Cardiology
cccccccc-cccc-cccc-cccc-cccccccccccc Dr. Emily Rodriguez Pediatrics

Example: Book an Appointment

curl -X POST http://localhost:5206/api/appointments \
  -H "Content-Type: application/json" \
  -d '{
    "patientId": "11111111-1111-1111-1111-111111111111",
    "doctorId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "start": "2025-01-15T09:00:00Z",
    "end": "2025-01-15T09:30:00Z",
    "notes": "Annual checkup"
  }'

Database

Default: In-memory database (no setup required)

Docker Compose (Recommended for PostgreSQL)

One command to start PostgreSQL:

# Start PostgreSQL container
docker compose up -d

# Apply migrations (first time only)
UseInMemoryDatabase=false dotnet ef database update --project src/Application --startup-project src/Api

# Run API with PostgreSQL
dotnet run --project src/Api --launch-profile Docker

Cleanup:

# Stop container (data preserved)
docker compose down

# Stop and delete all data
docker compose down -v

Manual PostgreSQL Setup

If you prefer not to use docker-compose, update src/Api/appsettings.json:

{
  "UseInMemoryDatabase": false,
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5432;Database=VerticalSliceDb;Username=postgres;Password=yourPassword"
  }
}

Database Migrations

# Add a new migration
dotnet ef migrations add "MigrationName" --project src/Application --startup-project src/Api --output-dir Infrastructure/Persistence/Migrations

# Apply migrations
dotnet ef database update --project src/Application --startup-project src/Api

Testing

# Run all tests
dotnet test

# Unit tests - Domain logic, validators
dotnet test tests/Application.UnitTests

# Integration tests - API smoke tests
dotnet test tests/Application.IntegrationTests

Learn More

License

MIT License