Skip to content

Repository files navigation


.NET C# EF Core License PRs Welcome


A production-ready apartment booking platform built on Clean Architecture, DDD, and CQRS — designed for maintainability, testability, and scalability.


📋 Table of Contents


🏠 Overview

Bookify is a full-featured apartment booking system built with C# and .NET, following Clean Architecture principles to ensure a modular, maintainable, and testable codebase.

The system allows users to search for apartments, make reservations, process payments, and leave reviews — all powered by a robust domain model with rich business rules.

Users ──► Search Apartments ──► Reserve ──► Confirm Booking ──► Review

Core Goals:

  • Clear separation of concerns across all architectural layers
  • Domain-centric design with rich business logic
  • Testable by design — every layer is independently verifiable
  • Production-ready patterns: Outbox, CQRS, Circuit Breaker, Optimistic Concurrency

🏛️ Architecture

Bookify follows a Domain-Centric Clean Architecture, where dependencies always flow inward — outer layers depend on inner layers, never the reverse.

┌─────────────────────────────────────────────┐
│              Presentation Layer              │  ← REST API, Controllers, Middleware
├─────────────────────────────────────────────┤
│              Application Layer              │  ← Use Cases, CQRS, MediatR, Validation
├─────────────────────────────────────────────┤
│               Domain Layer                  │  ← Entities, Value Objects, Domain Events
├─────────────────────────────────────────────┤
│            Infrastructure Layer             │  ← EF Core, RabbitMQ, Identity, Email
└─────────────────────────────────────────────┘

Key Architectural Principles:

Principle Description
Separation of Concerns Each layer has a single, well-defined responsibility
Dependency Inversion Inner layers define interfaces; outer layers implement them
Persistence Ignorance Domain has zero knowledge of how data is stored
Bounded Contexts Clear boundaries prevent domain logic leakage
Encapsulation State changes only happen through well-defined domain methods
Single Responsibility Every class has one reason to change

🔍 Layer Breakdown

🟣 Domain Layer — The Heart of the System

The core of the application. Contains pure business logic with no external dependencies.

// Example: Rich Domain Entity
public sealed class Booking : Entity
{
    public ApartmentId ApartmentId { get; private set; }
    public UserId UserId { get; private set; }
    public DateRange Duration { get; private set; }
    public Money TotalPrice { get; private set; }
    public BookingStatus Status { get; private set; }

    public static Result<Booking> Reserve(
        Apartment apartment,
        UserId userId,
        DateRange duration,
        PricingService pricingService)
    {
        // Rich business rules enforced here
    }
}

Contains: Entities · Value Objects · Domain Events · Domain Services · Repository Interfaces · Exceptions · Enums


🔴 Application Layer — Orchestrating the Domain

Coordinates domain objects to fulfill use cases. Implements CQRS via MediatR.

// Command: Reserve an Apartment
public record ReserveApartmentCommand(
    Guid ApartmentId,
    Guid UserId,
    DateOnly StartDate,
    DateOnly EndDate) : ICommand<Guid>;

// Handler
internal sealed class ReserveApartmentCommandHandler : ICommandHandler<ReserveApartmentCommand, Guid>
{
    public async Task<Result<Guid>> Handle(
        ReserveApartmentCommand request,
        CancellationToken cancellationToken)
    {
        // Orchestrate: fetch → validate → execute → persist → publish
    }
}

Contains: Commands & Queries · Handlers · Validators (FluentValidation) · Behaviors (Pipeline) · DTOs · Abstractions


🔵 Infrastructure Layer — External World Integration

Implements the interfaces defined by inner layers. Handles all I/O concerns.

// EF Core Repository Implementation
internal sealed class ApartmentRepository : IApartmentRepository
{
    private readonly ApplicationDbContext _context;

    public async Task<Apartment?> GetByIdAsync(
        ApartmentId id,
        CancellationToken cancellationToken = default) =>
        await _context.Apartments
            .FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
}

Contains: EF Core DbContext · Entity Configurations · Repositories · Keycloak Integration · RabbitMQ Publisher · Email Provider · Outbox Pattern · Background Jobs


🟢 Presentation Layer — System Entry Point

Exposes the application via a REST API. Thin layer — delegates everything to Application.

[ApiController]
[Route("api/v{version:apiVersion}/bookings")]
public class BookingsController : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> ReserveApartment(
        ReserveApartmentRequest request,
        CancellationToken cancellationToken)
    {
        var command = new ReserveApartmentCommand(/* map request */);
        var result = await _sender.Send(command, cancellationToken);
        return result.IsSuccess ? Ok(result.Value) : BadRequest(result.Error);
    }
}

Contains: Controllers / Minimal API Endpoints · Middleware · DI Composition Root · Docker Compose Setup · API Versioning


⚒️ Tech Stack

Backend Core

.NET C# ASP.NET Core MediatR

Data & Messaging

SQL Server EF Core RabbitMQ Redis

Security & Identity

Keycloak JWT

DevOps & Observability

Docker Serilog xUnit


🗂️ Domain Model

The system is centered around four core aggregates:

┌──────────────┐         ┌─────────────────────────────────────┐
│     User     │         │              Booking                 │
│──────────────│    ┌───►│──────────────────────────────────── │
│ Id: Guid     │    │    │ Id · ApartmentId · UserId           │
│ FirstName    │────┘    │ Start · End · PriceForPeriod        │
│ LastName     │         │ CleaningFee · AmenitiesUpCharge     │
│ Email        │         │ TotalPrice · Status                 │
└──────────────┘         │ CreatedOnUtc · ConfirmedOnUtc       │
                         └───────────────────┬─────────────────┘
┌──────────────────────┐                     │
│      Apartment       │                     │
│──────────────────────│                     ▼
│ Id · Name            │         ┌───────────────────┐
│ Description          │         │      Review        │
│ Address (VO)         │         │───────────────────│
│ Price (Money VO)     │         │ Id · ApartmentId  │
│ CleaningFee          │         │ BookingId · UserId│
│ Amenities[]          │         │ Rating · Comment  │
│ LastBookedOnUtc      │         └───────────────────┘
└──────────────────────┘

Value Objects: Money · Address · DateRange · ApartmentId · UserId · BookingId


🚀 Getting Started

Prerequisites

Run with Docker Compose

# Clone the repository
git clone https://github.com/Alnuimi/Bookify.git
cd Bookify

# Start all services (SQL Server, RabbitMQ, Redis, Keycloak)
docker-compose up -d

# Apply database migrations
dotnet ef database update --project src/Bookify.Infrastructure

# Run the API
dotnet run --project src/Bookify.Api

Run Locally

# Restore dependencies
dotnet restore

# Build the solution
dotnet build

# Run tests
dotnet test

# Start the API
dotnet run --project src/Bookify.Api

The API will be available at https://localhost:5001 with Swagger at https://localhost:5001/swagger.


📁 Project Structure

Bookify/
│
├── src/
│   ├── Bookify.Domain/              # 🟣 Core business logic
│   │   ├── Apartments/              
│   │   │   ├── Apartment.cs         # Aggregate root
│   │   │   ├── Address.cs           # Value object
│   │   │   └── Money.cs             # Value object
│   │   ├── Bookings/
│   │   │   ├── Booking.cs           # Aggregate root
│   │   │   ├── BookingStatus.cs     # Enum
│   │   │   └── Events/              # Domain events
│   │   └── Users/
│   │       └── User.cs
│   │
│   ├── Bookify.Application/         # 🔴 Use cases & orchestration
│   │   ├── Abstractions/
│   │   │   ├── ICommand.cs
│   │   │   └── IQuery.cs
│   │   ├── Apartments/
│   │   │   └── SearchApartments/    # Query + Handler
│   │   ├── Bookings/
│   │   │   ├── ReserveApartment/    # Command + Handler
│   │   │   └── ConfirmBooking/      # Command + Handler
│   │   └── Behaviors/               # Pipeline behaviors
│   │       ├── LoggingBehavior.cs
│   │       └── ValidationBehavior.cs
│   │
│   ├── Bookify.Infrastructure/      # 🔵 External integrations
│   │   ├── Data/
│   │   │   ├── ApplicationDbContext.cs
│   │   │   └── Configurations/      # EF entity configs
│   │   ├── Repositories/
│   │   ├── Authentication/          # Keycloak integration
│   │   ├── Messaging/               # RabbitMQ
│   │   ├── Outbox/                  # Outbox pattern
│   │   └── BackgroundJobs/
│   │
│   └── Bookify.Api/                 # 🟢 REST API entry point
│       ├── Controllers/
│       ├── Middleware/
│       └── Program.cs
│
└── tests/
    ├── Bookify.Domain.UnitTests/
    ├── Bookify.Application.UnitTests/
    ├── Bookify.Application.IntegrationTests/
    └── Bookify.Architecture.Tests/

🧪 Testing

The solution includes a comprehensive testing strategy across all layers:

# Run all tests
dotnet test

# Run with coverage
dotnet test --collect:"XPlat Code Coverage"
Test Type Target Tool
Unit Tests Domain logic, Application handlers xUnit + NSubstitute
Integration Tests Infrastructure, API endpoints WebApplicationFactory
Architecture Tests Layer dependency rules NetArchTest

Architecture tests enforce that:

  • Domain has zero external dependencies
  • Application only references Domain
  • Infrastructure does not reference Presentation
  • Controllers are thin and delegate to MediatR

🧩 Design Patterns

Pattern Location Purpose
CQRS Application Separate read/write models
Repository Domain/Infrastructure Persistence abstraction
Outbox Infrastructure Reliable domain event publishing
Unit of Work Infrastructure Transactional consistency
Pipeline Behavior Application Cross-cutting concerns (logging, validation)
Result Pattern Domain/Application Explicit error handling without exceptions
Optimistic Concurrency Infrastructure Conflict detection in concurrent updates
Domain Events Domain Decouple side effects from core logic

⭐ If this project helped you, consider giving it a star!

GitHub stars

About

Bookify is a c# application that allows users to manage their book collection using clean architecture principles and a layered architecture. The application is designed to be modular and maintainable, with clear separation of concerns between the different layers

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages