A practical flight-booking system built as microservices with .NET 10, using Vertical Slice Architecture, DDD, CQRS, Event Sourcing, gRPC, RabbitMQ, Wolverine, PostgreSQL, MongoDB and .NET Aspire.
Developed by Evangelos Vlachos.
- Overview
- Architecture
- Services
- Technology Stack
- Project Structure
- Getting Started
- API Documentation
- Development Tooling
- Contributing
This repository demonstrates how to design and run a production-style microservices system end to end: independent services with their own databases, asynchronous messaging with durable inbox/outbox, synchronous gRPC calls between services, an API gateway, centralized identity, observability and container/Kubernetes deployment.
Key goals:
- Vertical Slice Architecture with feature folders. Each request is one self-contained slice.
- Domain Driven Design for all business logic.
- CQRS with MediatR, plus validation and logging pipeline behaviours.
- Event Sourcing (EventStoreDB) for the write side of the Booking service.
- Event Driven Architecture with RabbitMQ on top of Wolverine, using durable inbox (idempotent, exactly-once processing) and outbox (at-least-once delivery) patterns.
- gRPC for internal service-to-service communication.
- PostgreSQL for write models, MongoDB for read models.
- Unit, integration, end-to-end and contract tests (NSubstitute, Testcontainers, PactNet).
- Observability with OpenTelemetry, Jaeger, Prometheus, Grafana and Serilog/Kibana.
- IdentityServer (OpenID Connect / OAuth2) for authentication and authorization.
- YARP as the API gateway.
- Docker Compose, Kubernetes (Nginx Ingress, cert-manager) and .NET Aspire for orchestration.
Each service owns its data and exposes a small REST surface through minimal APIs. Commands mutate the write store (PostgreSQL or EventStoreDB) and publish integration events through Wolverine's durable outbox to RabbitMQ. Consumers process those events through the durable inbox and project them into MongoDB read models. Queries read from MongoDB only. Cross-service reads that must be synchronous (for example Booking validating a flight or passenger) go over gRPC.
| Service | Responsibility | Write store | Read store |
|---|---|---|---|
| Identity | Users, roles, tokens (IdentityServer) | PostgreSQL | - |
| Flight | Flights, airports, aircraft, seats | PostgreSQL | MongoDB |
| Passenger | Passenger profiles | PostgreSQL | MongoDB |
| Booking | Booking a seat on a flight for a passenger | EventStoreDB | MongoDB |
| ApiGateway | Single public entry point (YARP) | - | - |
| Aspire AppHost | Local orchestration and dashboard | - | - |
- .NET 10, Minimal APIs, API Versioning
- MediatR, FluentValidation, Mapster
- Wolverine + RabbitMQ for messaging, MassTransit contracts
- gRPC with Grpc.AspNetCore
- Entity Framework Core + PostgreSQL
- MongoDB, EventStoreDB, Redis
- Duende IdentityServer (OpenID Connect / OAuth2)
- YARP reverse proxy
- OpenTelemetry, Jaeger, Prometheus, Grafana, Serilog + Kibana
- Polly resilience, ASP.NET Core Health Checks
- Scalar and Swagger for OpenAPI docs
- xUnit, NSubstitute, Testcontainers, PactNet, Bogus
- .NET Aspire, Docker, Kubernetes, Nginx Ingress, cert-manager
.
├── src
│ ├── ApiGateway/ # YARP gateway
│ ├── Aspire/ # Aspire AppHost and service defaults
│ ├── BuildingBlocks/ # Shared cross-cutting code (Core, EFCore, Mongo, Wolverine, Jwt, Logging, OpenTelemetry, Polly, TestBase, ...)
│ └── Services
│ ├── Booking/ # src/Booking, src/Booking.Api, tests/
│ ├── Flight/
│ ├── Identity/
│ └── Passenger/
├── deployments
│ ├── configs/ # otel-collector, prometheus, grafana configs
│ ├── docker-compose/ # infrastructure and full-stack compose files
│ └── kubernetes/ # manifests + cert-manager
├── assets/ # diagrams and logo
├── booking.rest # REST Client requests for manual API testing
└── booking-microservices.sln
Inside every service, code is grouped by feature (for example Flights/Features/CreatingFlight/V1/) rather than by technical layer. Each feature folder holds its endpoint, command/query, handler, validator and any events it raises, so a change to one use case touches only that folder.
- .NET 10 SDK
- Docker Desktop
- Node.js (for Husky commit hooks)
- Optional: .NET Aspire CLI,
kubectl
Create and trust a development HTTPS certificate so the containers can serve TLS.
Windows (PowerShell):
dotnet dev-certs https -ep $env:USERPROFILE\.aspnet\https\aspnetapp.pfx -p password
dotnet dev-certs https --trustmacOS / Linux:
dotnet dev-certs https -ep ${HOME}/.aspnet/https/aspnetapp.pfx -p password
dotnet dev-certs https --trustThe fastest way to get everything up locally, with a dashboard for logs, traces and metrics:
aspire runThe Aspire dashboard is available at http://localhost:18888.
Infrastructure only (RabbitMQ, PostgreSQL, EventStoreDB, MongoDB, Redis, Jaeger, Zipkin, OTel Collector, Prometheus, Grafana):
docker-compose -f ./deployments/docker-compose/docker-compose.infrastructure.yaml up -dFull stack including the services:
docker-compose -f ./deployments/docker-compose/docker-compose.yaml up -dInstall cert-manager first, then apply the TLS issuer and the application manifests:
kubectl apply -f ./deployments/kubernetes/booking-cert-manager.yml
kubectl apply -f ./deployments/kubernetes/booking-microservices.ymlThe manifests reference images under the
evangelosvlachos96/Docker Hub namespace. Build and push the service images there (or change the image names) before deploying.
Build the whole solution from the repository root:
dotnet buildRun a single service from its *.Api project folder (for example src/Services/Flight/src/Flight.Api):
dotnet runRun all tests (integration and end-to-end tests start their dependencies with Testcontainers, so Docker must be running):
dotnet testEvery service exposes OpenAPI documentation at /swagger (Swagger UI) and /scalar/v1 (Scalar).
For quick manual testing, open booking.rest with the VS Code REST Client extension. Seeded users are van1 / Admin@123456 (admin) and van2 / User@123456 (user).
.NET tools (CSharpier formatter, dotnet-outdated) are declared in .config/dotnet-tools.json:
dotnet tool restoreHusky + commitlint enforce Conventional Commits and run the formatter before each commit:
npm installUpgrade NuGet packages across the solution:
dotnet outdated -u

