Skip to content

komenday/flight-booking-system

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

31 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

✈️ Flight Booking System (FBS)

A production-grade .NET 10 flight seat reservation system built with Clean Architecture, Domain-Driven Design (DDD), and CQRS, fully deployed to Microsoft Azure.

.NET Architecture Azure CI/CD License

πŸ“‹ Table of Contents


πŸ” Overview

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.

Core Features

  • 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

πŸ—οΈ Architecture

Clean Architecture Layers

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               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  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Patterns

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

☁️ Azure Infrastructure

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)

Managed Identity RBAC

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

πŸ› οΈ Technologies

Core

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

Azure & Cloud

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

Observability & DevOps

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

Libraries

Library Purpose
Polly v8 / Microsoft.Extensions.Http.Resilience HTTP retry + circuit breaker
Bogus Flight seed data generation
Mailtrap API Email delivery (sandbox)

πŸ“ Project Structure

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

πŸ”Œ API Endpoints

Flights

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

Reservations

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

Example: Create Reservation

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"
}

🎯 Domain Model

Aggregates

Flight

  • Flight (AggregateRoot) β†’ owns Seat collection
  • 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:
    • ReservationMustBePendingToConfirmRule
    • ReservationMustNotBeExpiredToConfirmRule
    • CannotCancelNonPendingReservationRule (Pending only; Confirmed/Expired/Cancelled β†’ 409)
    • OnlyPendingReservationsCanExpireRule

Event Flow

FBS.API
  └─► CreateReservationCommandHandler
        β”œβ”€β–Ί Flight.ReserveSeat()  β†’ SeatReservedEvent (internal)
        β”œβ”€β–Ί Reservation.Create()  β†’ ReservationCreatedEvent
        └─► UnitOfWork.SaveChanges()
              └─► DomainEventDispatcher
                    └─► ServiceBusEventPublisher
                          └─► Service Bus Queue: reservation-events
                                └─► FBS.Function.Notifications
                                      └─► MailtrapApiEmailService

πŸš€ Getting Started

Prerequisites

Local Setup

1. Clone the repository

git clone https://github.com/komenday/flight-booking-system.git
cd flight-booking-system

2. 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.API

Migrations 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 run

Swagger 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

Database Migrations

# 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

βš™οΈ Configuration

FBS.API β€” appsettings.json

{
  "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"
  }
}

FBS.Function.Notifications β€” local.settings.json

{
  "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"
  }
}

FBS.Function.ExpiredReservations β€” local.settings.json

{
  "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;"
  }
}

πŸ”„ CI/CD

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

Required GitHub Secrets

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

πŸ“Š Monitoring

Application Insights

Shared across all three applications. Filter by cloud_RoleName in Log Analytics:

traces
| where cloud_RoleName == "fbs-dev"
| order by timestamp desc

New Relic APM

FBS.API (Linux) β€” OpenTelemetry OTLP integration:

  • Distributed traces for all HTTP requests and outbound HttpClient calls
  • Configurable via NewRelic:* app settings
  • Skips /health endpoint 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.


πŸ“„ License

This project is licensed under the MIT License.


πŸ”— Links