A production-grade .NET 10 flight seat reservation system built with Clean Architecture, Domain-Driven Design (DDD), and CQRS, fully deployed to Microsoft Azure.
- Overview
- Architecture
- Azure Infrastructure
- Technologies
- Project Structure
- API Endpoints
- Domain Model
- Getting Started
- Configuration
- CI/CD
- Monitoring
FBS allows passengers to search for flights, reserve seats, confirm or cancel reservations, and receive email notifications at each lifecycle stage. The system enforces a 10-minute confirmation window β unconfirmed reservations are automatically expired and their seats released.
- Flight Search β search available flights by route and date with real-time seat availability
- Seat Reservation β reserve a specific seat with conflict prevention via optimistic concurrency (RowVersion)
- Reservation Lifecycle β
Pending β Confirmed / Cancelled / Expired - Automatic Expiration β Azure Function Timer trigger expires unconfirmed reservations every 2 minutes
- Email Notifications β event-driven email notifications at every lifecycle transition via Mailtrap API
- Resilient HTTP β Polly v8 standard resilience pipeline (retry + circuit breaker) for outbound HTTP calls
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FBS.API (Presentation) β
β REST Controllers Β· GlobalExceptionHandler β
β OpenTelemetry (OTLP β New Relic) β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββ
β FBS.Application (Use Cases) β
β Commands Β· Queries Β· DTOs Β· FluentValidation β
β MediatR Pipeline: Logging Β· Validation Β· Tx β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββ
β FBS.Domain (Business Logic) β
β Aggregates Β· Value Objects Β· Domain Events β
β Business Rules Β· Specifications Β· Result<T> β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββ
β FBS.Infrastructure (Data & I/O) β
β EF Core Β· Repositories Β· UnitOfWork β
β ServiceBusEventPublisher Β· DomainEventDispatcher β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Pattern | Implementation |
|---|---|
| CQRS | Full separation: Commands (write) and Queries (read) via MediatR. Shared Azure SQL database. |
| DDD | Aggregates (Flight, Reservation), Value Objects (FlightNumber, SeatNumber, Email), Domain Events |
| Repository + Unit of Work | IFlightRepository, IReservationRepository, IUnitOfWork β EF Core implementations in Infrastructure |
| Specification Pattern | ExpiredReservationsSpecification, ReservationsByFlightSpecification for reusable query logic |
| Pipeline Behaviors | ValidationBehavior (FluentValidation), TransactionBehavior (EF Core), LoggingBehavior |
| Result Pattern | Result<T> with ErrorType instead of throwing exceptions for business failures |
| Domain Events | Raised inside aggregates, dispatched after SaveChanges via IDomainEventDispatcher |
| Managed Identity | Passwordless auth from App Service and Functions to SQL, Service Bus, Storage |
All infrastructure is described as code with Bicep (infra/) and deployed via GitHub Actions.
Azure Resource Group: rg-fbs-dev (West Europe)
β
βββ App Service (Linux B1) fbs-dev
β βββ FBS.API β Azure SQL (Poland Central)
β
βββ Azure Function (Consumption) fbns-func-dev
β βββ FBS.Function.Notifications
β Service Bus Trigger β Mailtrap API
β
βββ Azure Function (Consumption) expire-func-dev
β βββ FBS.Function.ExpiredReservations
β Timer Trigger (every 2 min) β Azure SQL
β
βββ Azure Service Bus (Basic) fbs-servicebus-dev
β βββ Queue: reservation-events
β
βββ Azure SQL (Basic 5 DTU) fbs-sql-server-dev / fbs-db-dev
β
βββ Storage Account storagefbsdev
β (Functions runtime state, Timer trigger locks)
β
βββ Application Insights fbs-dev
(shared across all three applications)
| Principal | Resource | Role |
|---|---|---|
fbs-dev |
Azure SQL | db_datareader + db_datawriter |
fbs-dev |
Service Bus | Azure Service Bus Data Sender |
fbns-func-dev |
Service Bus | Azure Service Bus Data Receiver |
fbns-func-dev |
Storage | Blob Owner Β· Queue Contributor Β· Table Contributor |
expire-func-dev |
Azure SQL | db_datareader + db_datawriter |
expire-func-dev |
Service Bus | Azure Service Bus Data Sender |
expire-func-dev |
Storage | Blob Owner Β· Queue Contributor Β· Table Contributor |
| Technology | Version | Purpose |
|---|---|---|
| .NET / ASP.NET Core | 10.0 | API framework |
| Entity Framework Core | 10.0 | ORM, Code-First migrations |
| Azure SQL | β | Relational database |
| MediatR | 12.x | CQRS, pipeline behaviors |
| FluentValidation | 11.x | Request validation |
| Technology | Purpose |
|---|---|
| Azure App Service (Linux) | Hosts FBS.API |
| Azure Functions v4 (Windows, isolated) | Notifications and expiration background processing |
| Azure Service Bus | Event-driven messaging between API and Functions |
| Azure SQL | Database with Entra-ID-only authentication |
| Azure Storage | Functions runtime state and distributed timer locks |
| Azure Managed Identity | Passwordless auth across all Azure services |
| Technology | Purpose |
|---|---|
| Application Insights | Telemetry, live metrics, log stream |
| New Relic APM | Distributed tracing, error inbox, performance dashboards |
| OpenTelemetry (OTLP/HTTP) | Vendor-neutral instrumentation for Linux App Service |
| GitHub Actions | 4 CI/CD pipelines (Infrastructure + 3 app deployments) |
| Bicep | Infrastructure as Code |
| OIDC Workload Identity Federation | Passwordless GitHub β Azure authentication |
| Library | Purpose |
|---|---|
| Polly v8 / Microsoft.Extensions.Http.Resilience | HTTP retry + circuit breaker |
| Bogus | Flight seed data generation |
| Mailtrap API | Email delivery (sandbox) |
flight-booking-system/
βββ src/
β βββ FBS.API/ # ASP.NET Core Web API (Linux App Service)
β β βββ Controllers/ # FlightsController, ReservationsController
β β βββ DTOs/ # Request DTOs
β β βββ Middlewares/ # GlobalExceptionHandler (ValidationProblemDetails)
β β βββ Program.cs # App bootstrap, OpenTelemetry setup
β β
β βββ FBS.Application/ # Use cases (no framework dependencies)
β β βββ Commands/ # CreateReservation, ConfirmReservation,
β β β # CancelReservation, ExpireReservation
β β βββ Queries/ # GetAvailableFlights, GetFlightByNumber,
β β β # GetReservation
β β βββ Common/
β β βββ Behaviors/ # ValidationBehavior, TransactionBehavior,
β β β # LoggingBehavior
β β βββ Result/ # Result<T>, ErrorType
β β
β βββ FBS.Domain/ # Pure business logic, no dependencies
β β βββ Flight/ # Flight aggregate, Seat, Value Objects,
β β β # SeatReservedEvent, SeatReleasedEvent
β β βββ Reservation/ # Reservation aggregate, Value Objects,
β β β # ReservationCreated/Confirmed/Cancelled/ExpiredEvent
β β βββ Common/
β β β βββ Base/ # AggregateRoot<T>, Entity<T>, DomainEventBase
β β β βββ Rules/ # Business rule classes + IBusinessRule
β β β βββ Specifications/ # Specification<T>, ExpiredReservationsSpecification
β β βββ Repositories/ # IFlightRepository, IReservationRepository, IUnitOfWork
β β βββ SharedKernel/ # Email, PhoneNumber, Airport value objects
β β
β βββ FBS.Infrastructure/ # EF Core, repositories, external services
β β βββ Persistence/ # ApplicationDbContext, EF configs, migrations
β β βββ EventDispatcher/ # IDomainEventDispatcher, post-save dispatch
β β βββ Events/ # ServiceBusEventPublisher, EventMapper, DTOs
β β βββ BackgroundJobs/ # ExpireReservationsJob (used by Azure Function)
β β βββ Seed/ # FlightDataSeeder (Bogus)
β β
β βββ FBS.Function.Notifications/ # Azure Function β Service Bus trigger
β β βββ Functions/ # ProcessReservationEventFunction
β β β # Complete/Abandon/DeadLetter settlement
β β βββ Notification/ # NotificationService
β β βββ Email/ # MailtrapApiEmailService, options
β β βββ Templates/ # EmailTemplates (HTML)
β β
β βββ FBS.Function.ExpiredReservations/ # Azure Function β Timer trigger
β βββ Functions/ # ExpireReservationsFunction (every 2 min)
β
βββ infra/ # Bicep Infrastructure as Code
β βββ main.bicep
β βββ main.bicepparam
β βββ modules/ # app-insights, api-app, function-app,
β # service-bus, sql, storage
β
βββ .github/workflows/
β βββ 01-infrastructure.yml # Bicep deploy (on infra/** changes)
β βββ 02-deploy-api.yml # Build + deploy FBS.API
β βββ 03-deploy-notifications.yml # Build + zip-deploy FBS.Function.Notifications
β βββ 04-deploy-expire.yml # Build + zip-deploy FBS.Function.ExpiredReservations
β
βββ postman/
β βββ FBS-API.postman_collection.json # 26 test cases with pre-request scripts
β βββ FBS-Local.postman_environment.json
β βββ FBS-AzureDev.postman_environment.json
β
βββ scripts/
βββ setup-rbac.ps1 # One-time RBAC role assignment script
| Method | Endpoint | Description | Response |
|---|---|---|---|
GET |
/api/flights/search?departureAirport=BCN&arrivalAirport=ATL&date=2026-04-08 |
Search flights by route and date | 200 FlightSummaryDto[] |
GET |
/api/flights/{flightNumber} |
Get flight details with full seat map | 200 FlightDetailsDto / 404 |
| Method | Endpoint | Description | Response |
|---|---|---|---|
POST |
/api/reservations/create |
Create a new Pending reservation | 201 CreateReservationResponse |
GET |
/api/reservations/get/{id} |
Get reservation details | 200 ReservationDetailsDto / 404 |
PUT |
/api/reservations/confirm/{id} |
Confirm a Pending reservation | 204 / 409 |
DELETE |
/api/reservations/cancel/{id} |
Cancel a Pending reservation | 204 / 409 |
POST /api/reservations/create
{
"flightNumber": "AA387",
"seatNumber": "5C",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phone": "+380971234567"
}201 Created:
{
"reservationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"flightId": "fb29e71f-5219-4dc9-8b8b-daf709de2afb",
"flightNumber": "AA387",
"seatNumber": "5C",
"passengerName": "John Doe",
"status": "Pending",
"createdAt": "2026-04-08T10:00:00Z",
"expiresAt": "2026-04-08T10:10:00Z"
}Flight
Flight(AggregateRoot) β ownsSeatcollection- Value Objects:
FlightId,FlightNumber,SeatNumber,Airport - Domain Events:
SeatReservedEvent,SeatReleasedEvent - Business Rules: seat must be available to reserve; seat must be reserved to release
Reservation
Reservation(AggregateRoot)- Value Objects:
ReservationId,PassengerInfo,Email,PhoneNumber - States:
Pending β Confirmed | Cancelled | Expired - Domain Events:
ReservationCreatedEvent,ReservationConfirmedEvent,ReservationCancelledEvent,ReservationExpiredEvent - Business Rules:
ReservationMustBePendingToConfirmRuleReservationMustNotBeExpiredToConfirmRuleCannotCancelNonPendingReservationRule(Pending only; Confirmed/Expired/Cancelled β 409)OnlyPendingReservationsCanExpireRule
FBS.API
βββΊ CreateReservationCommandHandler
βββΊ Flight.ReserveSeat() β SeatReservedEvent (internal)
βββΊ Reservation.Create() β ReservationCreatedEvent
βββΊ UnitOfWork.SaveChanges()
βββΊ DomainEventDispatcher
βββΊ ServiceBusEventPublisher
βββΊ Service Bus Queue: reservation-events
βββΊ FBS.Function.Notifications
βββΊ MailtrapApiEmailService
- .NET 10 SDK
- SQL Server (LocalDB is sufficient for local development)
- Azure Functions Core Tools v4 (for running Functions locally)
- Azurite (local Azure Storage emulator)
- Azure CLI (
az loginfor Managed Identity local development)
1. Clone the repository
git clone https://github.com/komenday/flight-booking-system.git
cd flight-booking-system2. Configure appsettings.Development.json in FBS.API
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=FBS_Dev;Trusted_Connection=True;"
}
}3. Apply migrations and seed data
cd src/FBS.Infrastructure
dotnet ef database update --startup-project ../FBS.APIMigrations run automatically on startup. Seed data (15 random flights) is inserted if the Flights table is empty.
4. Run the API
cd src/FBS.API
dotnet runSwagger UI: http://localhost:5000/swagger
5. Run Functions locally (optional)
Start Azurite, then in separate terminals:
cd src/FBS.Function.Notifications
func start
cd src/FBS.Function.ExpiredReservations
func start# Add a new migration
dotnet ef migrations add MigrationName \
--project src/FBS.Infrastructure \
--startup-project src/FBS.API
# Apply migrations
dotnet ef database update \
--project src/FBS.Infrastructure \
--startup-project src/FBS.API{
"ConnectionStrings": {
"DefaultConnection": ""
},
"ServiceBus": {
"FullyQualifiedNamespace": "fbs-servicebus-dev.servicebus.windows.net",
"QueueName": "reservation-events",
"TimeoutSeconds": 30,
"MaxRetryAttempts": 3
},
"EventPublisher": {
"BaseUrl": "",
"RetryCount": 3,
"TimeoutSeconds": 30
},
"NewRelic": {
"LicenseKey": "",
"ServiceName": "FBS-API",
"OtlpEndpoint": "https://otlp.eu01.nr-data.net:4318"
}
}{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"ServiceBusConnection__fullyQualifiedNamespace": "fbs-servicebus-dev.servicebus.windows.net",
"MailtrapApi__ApiToken": "YOUR_TOKEN",
"MailtrapApi__InboxId": "YOUR_INBOX_ID",
"MailtrapApi__FromEmail": "noreply@flightbooking.com",
"MailtrapApi__FromName": "Flight Booking System"
}
}{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"ServiceBus__FullyQualifiedNamespace": "fbs-servicebus-dev.servicebus.windows.net",
"ServiceBus__QueueName": "reservation-events",
"EventPublisher__BaseUrl": "https://placeholder.local",
"EventPublisher__RetryCount": "3",
"EventPublisher__TimeoutSeconds": "30"
},
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=FBS_Dev;Trusted_Connection=True;"
}
}Four GitHub Actions workflows with OIDC Workload Identity Federation (no client secrets):
| Workflow | Trigger | What it does |
|---|---|---|
01-infrastructure.yml |
Push to infra/** or manual |
Deploys Bicep β validates then applies Incremental |
02-deploy-api.yml |
Push to src/FBS.API/** or shared layers |
Build β Publish β Configure App Settings β Deploy β Health check |
03-deploy-notifications.yml |
Push to src/FBS.Function.Notifications/** |
Build β Publish β Zip β Configure App Settings β Zip Deploy |
04-deploy-expire.yml |
Push to src/FBS.Function.ExpiredReservations/** |
Build β Publish β Zip β Configure App Settings β Zip Deploy |
| Secret | Used in |
|---|---|
AZURE_CLIENT_ID |
All workflows (OIDC) |
AZURE_TENANT_ID |
All workflows (OIDC) |
AZURE_SUBSCRIPTION_ID |
All workflows (OIDC) |
SQL_CONNECTION_STRING |
02, 04 |
MAILTRAP_API_TOKEN |
03 |
MAILTRAP_INBOX_ID |
03 |
NEW_RELIC_LICENSE_KEY |
02, 03, 04 |
Shared across all three applications. Filter by cloud_RoleName in Log Analytics:
traces
| where cloud_RoleName == "fbs-dev"
| order by timestamp descFBS.API (Linux) β OpenTelemetry OTLP integration:
- Distributed traces for all HTTP requests and outbound HttpClient calls
- Configurable via
NewRelic:*app settings - Skips
/healthendpoint to reduce noise
FBS.Function.Notifications / FBS.Function.ExpiredReservations (Windows) β Native New Relic .NET Agent via NewRelic.Agent NuGet package:
- Auto-instruments Service Bus message processing, HTTP, async workflows
- Configured via
NEW_RELIC_*environment variables +CORECLR_*profiler settings
All three services appear under APM & Services in New Relic UI.
This project is licensed under the MIT License.
- Repository: github.com/komenday/flight-booking-system
- Live API: fbs-dev.azurewebsites.net/swagger