CS 1 Parking Lot System for LLD - IndianTechnoEra
Latest update Android YouTube

CS 1 Parking Lot System for LLD

Chapter 8: Case Study - Parking Lot System

Series: Low Level Design for .NET Developers | Previous: Chapter 7: Domain Driven Design Basics | Next: Chapter 9: Case Study - Splitwise


📖 Introduction

The Parking Lot System is one of the most common Low Level Design interview questions. It tests your understanding of:

  • Object Oriented Design principles
  • SOLID principles
  • Design patterns (Strategy, Observer, Factory, State)
  • Domain modeling
  • Concurrency handling
  • Pricing strategies

In this case study, we'll build a complete parking lot system from scratch, applying all the concepts we've learned throughout the series.


1. Requirements Analysis

1.1 Functional Requirements

  • The parking lot should have multiple floors
  • Each floor has multiple parking spots of different types (Compact, Regular, Large)
  • Different vehicle types can park (Motorcycle, Car, Truck)
  • Each vehicle gets a ticket upon entry with entry time
  • Payment is calculated based on duration and vehicle type
  • Support different pricing strategies (hourly, flat rate, etc.)
  • Track available spots per floor and spot type
  • Support entry and exit gates
  • Generate payment receipts

1.2 Non-Functional Requirements

  • System should handle concurrent entries/exits
  • Low latency for ticket generation
  • Accurate payment calculation
  • Audit trail of all transactions

1.3 Domain Language (Ubiquitous Language)

  • Parking Lot - The overall facility
  • Floor - A level within the parking lot
  • Parking Spot - A specific spot where vehicles park
  • Vehicle - The entity that parks
  • Ticket - Entry/exit document
  • Payment - Payment for parking
  • Receipt - Proof of payment

2. Domain Model

2.1 Core Enums

namespace ParkingLot.Domain
{
    public enum VehicleType
    {
        Motorcycle,
        Car,
        Truck
    }
    
    public enum ParkingSpotType
    {
        Compact,   // For motorcycles
        Regular,   // For cars
        Large      // For trucks
    }
    
    public enum TicketStatus
    {
        Active,
        Paid,
        Exited
    }
    
    public enum PaymentMethod
    {
        Cash,
        CreditCard,
        DebitCard,
        MobilePayment
    }
    
    public enum PaymentStatus
    {
        Pending,
        Completed,
        Failed,
        Refunded
    }
}

2.2 Value Objects

namespace ParkingLot.Domain.ValueObjects
{
    // Money value object
    public class Money : IEquatable<Money>
    {
        public decimal Amount { get; }
        public string Currency { get; }
        
        public Money(decimal amount, string currency = "USD")
        {
            if (amount < 0)
                throw new ArgumentException("Amount cannot be negative", nameof(amount));
                
            if (string.IsNullOrWhiteSpace(currency))
                throw new ArgumentException("Currency is required", nameof(currency));
                
            Amount = amount;
            Currency = currency.ToUpperInvariant();
        }
        
        public Money Add(Money other)
        {
            if (Currency != other.Currency)
                throw new InvalidOperationException($"Cannot add different currencies: {Currency} and {other.Currency}");
                
            return new Money(Amount + other.Amount, Currency);
        }
        
        public Money Subtract(Money other)
        {
            if (Currency != other.Currency)
                throw new InvalidOperationException($"Cannot subtract different currencies: {Currency} and {other.Currency}");
                
            return new Money(Amount - other.Amount, Currency);
        }
        
        public Money Multiply(decimal multiplier)
        {
            return new Money(Amount * multiplier, Currency);
        }
        
        public static Money Zero(string currency = "USD") => new Money(0, currency);
        
        public override bool Equals(object obj) => Equals(obj as Money);
        public bool Equals(Money other) => other != null && Amount == other.Amount && Currency == other.Currency;
        public override int GetHashCode() => HashCode.Combine(Amount, Currency);
        public override string ToString() => $"{Amount:F2} {Currency}";
    }
    
    // Duration value object
    public class Duration
    {
        public TimeSpan Value { get; }
        
        public Duration(TimeSpan value)
        {
            if (value < TimeSpan.Zero)
                throw new ArgumentException("Duration cannot be negative", nameof(value));
                
            Value = value;
        }
        
        public double TotalHours => Value.TotalHours;
        public double TotalMinutes => Value.TotalMinutes;
        
        public static Duration FromDateTime(DateTime start, DateTime end)
        {
            return new Duration(end - start);
        }
        
        public override string ToString()
        {
            if (Value.TotalHours >= 24)
                return $"{Value.Days}d {Value.Hours}h {Value.Minutes}m";
            if (Value.TotalHours >= 1)
                return $"{Value.Hours}h {Value.Minutes}m";
            return $"{Value.Minutes}m";
        }
    }
    
    // TimeSlot value object
    public class TimeSlot
    {
        public DateTime EntryTime { get; }
        public DateTime? ExitTime { get; private set; }
        public Duration Duration => ExitTime.HasValue ? Duration.FromDateTime(EntryTime, ExitTime.Value) : null;
        
        public TimeSlot(DateTime entryTime)
        {
            EntryTime = entryTime;
        }
        
        public void Exit(DateTime exitTime)
        {
            if (exitTime < EntryTime)
                throw new ArgumentException("Exit time cannot be before entry time", nameof(exitTime));
                
            ExitTime = exitTime;
        }
        
        public bool IsActive => !ExitTime.HasValue;
    }
}

2.3 Vehicle Entities

namespace ParkingLot.Domain.Entities
{
    public abstract class Vehicle
    {
        public string LicensePlate { get; }
        public VehicleType Type { get; }
        
        protected Vehicle(string licensePlate, VehicleType type)
        {
            if (string.IsNullOrWhiteSpace(licensePlate))
                throw new ArgumentException("License plate is required", nameof(licensePlate));
                
            LicensePlate = licensePlate.ToUpperInvariant();
            Type = type;
        }
        
        public abstract ParkingSpotType GetRequiredSpotType();
        
        public override bool Equals(object obj)
        {
            if (obj is not Vehicle other) return false;
            return LicensePlate == other.LicensePlate;
        }
        
        public override int GetHashCode() => LicensePlate.GetHashCode();
        public override string ToString() => $"{Type} ({LicensePlate})";
    }
    
    public class Motorcycle : Vehicle
    {
        public Motorcycle(string licensePlate) : base(licensePlate, VehicleType.Motorcycle) { }
        
        public override ParkingSpotType GetRequiredSpotType() => ParkingSpotType.Compact;
    }
    
    public class Car : Vehicle
    {
        public Car(string licensePlate) : base(licensePlate, VehicleType.Car) { }
        
        public override ParkingSpotType GetRequiredSpotType() => ParkingSpotType.Regular;
    }
    
    public class Truck : Vehicle
    {
        public Truck(string licensePlate) : base(licensePlate, VehicleType.Truck) { }
        
        public override ParkingSpotType GetRequiredSpotType() => ParkingSpotType.Large;
    }
    
    public class VehicleFactory
    {
        public static Vehicle Create(string licensePlate, VehicleType type)
        {
            return type switch
            {
                VehicleType.Motorcycle => new Motorcycle(licensePlate),
                VehicleType.Car => new Car(licensePlate),
                VehicleType.Truck => new Truck(licensePlate),
                _ => throw new NotSupportedException($"Vehicle type {type} not supported")
            };
        }
    }
}

2.4 Parking Spot Entity

namespace ParkingLot.Domain.Entities
{
    public class ParkingSpot
    {
        public int Id { get; }
        public int FloorId { get; }
        public int SpotNumber { get; }
        public ParkingSpotType Type { get; }
        public bool IsOccupied { get; private set; }
        public Vehicle CurrentVehicle { get; private set; }
        
        public ParkingSpot(int id, int floorId, int spotNumber, ParkingSpotType type)
        {
            Id = id;
            FloorId = floorId;
            SpotNumber = spotNumber;
            Type = type;
            IsOccupied = false;
        }
        
        public bool CanFitVehicle(Vehicle vehicle)
        {
            if (IsOccupied) return false;
            
            return vehicle.GetRequiredSpotType() switch
            {
                ParkingSpotType.Compact => true, // Compact spots fit all
                ParkingSpotType.Regular => Type != ParkingSpotType.Compact, // Regular spots don't fit in compact
                ParkingSpotType.Large => Type == ParkingSpotType.Large, // Large only fits large
                _ => false
            };
        }
        
        public void ParkVehicle(Vehicle vehicle)
        {
            if (!CanFitVehicle(vehicle))
                throw new InvalidOperationException($"Vehicle {vehicle.LicensePlate} cannot park in spot {SpotNumber}");
                
            CurrentVehicle = vehicle;
            IsOccupied = true;
        }
        
        public void Vacate()
        {
            CurrentVehicle = null;
            IsOccupied = false;
        }
        
        public override string ToString() => $"Floor {FloorId} - Spot {SpotNumber} ({Type})";
    }
}

2.5 Ticket Aggregate Root

namespace ParkingLot.Domain.Aggregates
{
    public class ParkingTicket : IAggregateRoot
    {
        private readonly List<DomainEvent> _domainEvents = new();
        
        public string TicketNumber { get; }
        public string LicensePlate { get; }
        public VehicleType VehicleType { get; }
        public int ParkingSpotId { get; }
        public int FloorId { get; }
        public DateTime EntryTime { get; }
        public DateTime? ExitTime { get; private set; }
        public TicketStatus Status { get; private set; }
        public Money AmountPaid { get; private set; }
        public PaymentMethod? PaymentMethod { get; private set; }
        public string PaymentTransactionId { get; private set; }
        
        public IReadOnlyCollection<DomainEvent> DomainEvents => _domainEvents.AsReadOnly();
        
        private ParkingTicket() { } // For persistence
        
        public ParkingTicket(string licensePlate, VehicleType vehicleType, int parkingSpotId, int floorId)
        {
            TicketNumber = GenerateTicketNumber();
            LicensePlate = licensePlate;
            VehicleType = vehicleType;
            ParkingSpotId = parkingSpotId;
            FloorId = floorId;
            EntryTime = DateTime.UtcNow;
            Status = TicketStatus.Active;
            AmountPaid = Money.Zero();
            
            AddDomainEvent(new TicketIssuedEvent(this));
        }
        
        public void Pay(Money amount, PaymentMethod paymentMethod, string transactionId)
        {
            if (Status != TicketStatus.Active)
                throw new InvalidOperationException($"Cannot pay for ticket in {Status} status");
                
            if (amount.Amount <= 0)
                throw new ArgumentException("Payment amount must be positive", nameof(amount));
                
            AmountPaid = amount;
            PaymentMethod = paymentMethod;
            PaymentTransactionId = transactionId;
            Status = TicketStatus.Paid;
            
            AddDomainEvent(new TicketPaidEvent(this));
        }
        
        public void Exit(DateTime exitTime)
        {
            if (Status != TicketStatus.Paid)
                throw new InvalidOperationException("Ticket must be paid before exit");
                
            if (exitTime < EntryTime)
                throw new ArgumentException("Exit time cannot be before entry time", nameof(exitTime));
                
            ExitTime = exitTime;
            Status = TicketStatus.Exited;
            
            AddDomainEvent(new TicketExitedEvent(this));
        }
        
        public Duration GetDuration()
        {
            var endTime = ExitTime ?? DateTime.UtcNow;
            return Duration.FromDateTime(EntryTime, endTime);
        }
        
        private static string GenerateTicketNumber()
        {
            return $"TKT-{DateTime.Now:yyyyMMddHHmmss}-{Guid.NewGuid().ToString().Substring(0, 6).ToUpper()}";
        }
        
        private void AddDomainEvent(DomainEvent eventItem)
        {
            _domainEvents.Add(eventItem);
        }
        
        public void ClearDomainEvents()
        {
            _domainEvents.Clear();
        }
    }
    
    // Domain Events
    public abstract class TicketDomainEvent : DomainEvent
    {
        public ParkingTicket Ticket { get; }
        
        protected TicketDomainEvent(ParkingTicket ticket)
        {
            Ticket = ticket;
        }
    }
    
    public class TicketIssuedEvent : TicketDomainEvent
    {
        public TicketIssuedEvent(ParkingTicket ticket) : base(ticket) { }
    }
    
    public class TicketPaidEvent : TicketDomainEvent
    {
        public TicketPaidEvent(ParkingTicket ticket) : base(ticket) { }
    }
    
    public class TicketExitedEvent : TicketDomainEvent
    {
        public TicketExitedEvent(ParkingTicket ticket) : base(ticket) { }
    }
}

3. Pricing Strategy (Strategy Pattern)

namespace ParkingLot.Domain.Strategies
{
    public interface IPricingStrategy
    {
        string StrategyName { get; }
        Money CalculateCharge(Duration duration, VehicleType vehicleType);
        bool IsApplicable(VehicleType vehicleType);
    }
    
    // Hourly pricing strategy
    public class HourlyPricingStrategy : IPricingStrategy
    {
        private readonly Dictionary<VehicleType, decimal> _hourlyRates;
        
        public string StrategyName => "Hourly Pricing";
        
        public HourlyPricingStrategy()
        {
            _hourlyRates = new Dictionary<VehicleType, decimal>
            {
                [VehicleType.Motorcycle] = 1.00m,
                [VehicleType.Car] = 2.50m,
                [VehicleType.Truck] = 5.00m
            };
        }
        
        public Money CalculateCharge(Duration duration, VehicleType vehicleType)
        {
            var rate = _hourlyRates[vehicleType];
            var hours = (int)Math.Ceiling(duration.TotalHours);
            hours = Math.Max(1, hours); // Minimum 1 hour
            
            var amount = rate * hours;
            
            // Daily max
            if (hours >= 24)
            {
                var days = hours / 24;
                var maxDailyRate = rate * 8; // Max 8 hours charge per day
                amount = Math.Min(amount, days * maxDailyRate);
            }
            
            return new Money(amount);
        }
        
        public bool IsApplicable(VehicleType vehicleType) => true;
    }
    
    // Flat rate pricing strategy
    public class FlatRatePricingStrategy : IPricingStrategy
    {
        private readonly Dictionary<VehicleType, decimal> _flatRates;
        
        public string StrategyName => "Flat Rate Pricing";
        
        public FlatRatePricingStrategy()
        {
            _flatRates = new Dictionary<VehicleType, decimal>
            {
                [VehicleType.Motorcycle] = 5.00m,
                [VehicleType.Car] = 10.00m,
                [VehicleType.Truck] = 20.00m
            };
        }
        
        public Money CalculateCharge(Duration duration, VehicleType vehicleType)
        {
            // First hour free, then flat rate
            if (duration.TotalHours <= 1)
                return new Money(0);
                
            return new Money(_flatRates[vehicleType]);
        }
        
        public bool IsApplicable(VehicleType vehicleType) => true;
    }
    
    // Weekend pricing strategy
    public class WeekendPricingStrategy : IPricingStrategy
    {
        private readonly IPricingStrategy _baseStrategy;
        
        public string StrategyName => "Weekend Pricing";
        
        public WeekendPricingStrategy(IPricingStrategy baseStrategy)
        {
            _baseStrategy = baseStrategy;
        }
        
        public Money CalculateCharge(Duration duration, VehicleType vehicleType)
        {
            var baseCharge = _baseStrategy.CalculateCharge(duration, vehicleType);
            
            // 20% surcharge on weekends
            if (IsWeekend(DateTime.UtcNow))
            {
                return new Money(baseCharge.Amount * 1.2m);
            }
            
            return baseCharge;
        }
        
        private bool IsWeekend(DateTime date)
        {
            return date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
        }
        
        public bool IsApplicable(VehicleType vehicleType) => true;
    }
    
    // Early bird pricing strategy
    public class EarlyBirdPricingStrategy : IPricingStrategy
    {
        public string StrategyName => "Early Bird Pricing";
        
        public Money CalculateCharge(Duration duration, VehicleType vehicleType)
        {
            // Flat rate for entry before 9 AM and exit after 6 PM
            var entryTime = DateTime.UtcNow - duration.Value;
            
            if (entryTime.Hour < 9 && DateTime.UtcNow.Hour >= 18)
            {
                return vehicleType switch
                {
                    VehicleType.Motorcycle => new Money(8.00m),
                    VehicleType.Car => new Money(12.00m),
                    VehicleType.Truck => new Money(20.00m),
                    _ => new Money(10.00m)
                };
            }
            
            // Fall back to hourly pricing
            var hourlyStrategy = new HourlyPricingStrategy();
            return hourlyStrategy.CalculateCharge(duration, vehicleType);
        }
        
        public bool IsApplicable(VehicleType vehicleType) => true;
    }
}

4. Payment Processing (Strategy Pattern)

namespace ParkingLot.Domain.Strategies
{
    public interface IPaymentMethod
    {
        string MethodName { get; }
        Task<PaymentResult> ProcessPaymentAsync(Money amount, Dictionary<string, string> paymentDetails);
    }
    
    public class CashPaymentMethod : IPaymentMethod
    {
        public string MethodName => "Cash";
        
        public Task<PaymentResult> ProcessPaymentAsync(Money amount, Dictionary<string, string> paymentDetails)
        {
            // Cash payment is handled at the gate
            return Task.FromResult(PaymentResult.Success(Guid.NewGuid().ToString()));
        }
    }
    
    public class CreditCardPaymentMethod : IPaymentMethod
    {
        private readonly ILogger<CreditCardPaymentMethod> _logger;
        
        public string MethodName => "Credit Card";
        
        public CreditCardPaymentMethod(ILogger<CreditCardPaymentMethod> logger)
        {
            _logger = logger;
        }
        
        public async Task<PaymentResult> ProcessPaymentAsync(Money amount, Dictionary<string, string> paymentDetails)
        {
            _logger.LogInformation("Processing credit card payment of {Amount}", amount);
            
            // Validate card details
            if (!paymentDetails.TryGetValue("CardNumber", out var cardNumber) ||
                !paymentDetails.TryGetValue("Expiry", out var expiry) ||
                !paymentDetails.TryGetValue("CVV", out var cvv))
            {
                return PaymentResult.Failed("Missing credit card details");
            }
            
            // Simulate payment gateway call
            await Task.Delay(500);
            
            // Validate card (simplified)
            if (cardNumber.Length != 16)
                return PaymentResult.Failed("Invalid card number");
                
            if (string.IsNullOrEmpty(cvv) || cvv.Length != 3)
                return PaymentResult.Failed("Invalid CVV");
            
            _logger.LogInformation("Credit card payment of {Amount} processed successfully", amount);
            
            return PaymentResult.Success($"CC-{DateTime.Now.Ticks}");
        }
    }
    
    public class MobilePaymentMethod : IPaymentMethod
    {
        private readonly ILogger<MobilePaymentMethod> _logger;
        
        public string MethodName => "Mobile Payment";
        
        public MobilePaymentMethod(ILogger<MobilePaymentMethod> logger)
        {
            _logger = logger;
        }
        
        public async Task<PaymentResult> ProcessPaymentAsync(Money amount, Dictionary<string, string> paymentDetails)
        {
            _logger.LogInformation("Processing mobile payment of {Amount}", amount);
            
            if (!paymentDetails.TryGetValue("PhoneNumber", out var phoneNumber) ||
                !paymentDetails.TryGetValue("Provider", out var provider))
            {
                return PaymentResult.Failed("Missing mobile payment details");
            }
            
            await Task.Delay(300);
            
            _logger.LogInformation("Mobile payment of {Amount} processed successfully via {Provider}", amount, provider);
            
            return PaymentResult.Success($"MP-{DateTime.Now.Ticks}");
        }
    }
    
    public class PaymentResult
    {
        public bool IsSuccess { get; }
        public string TransactionId { get; }
        public string ErrorMessage { get; }
        
        private PaymentResult(bool isSuccess, string transactionId, string errorMessage)
        {
            IsSuccess = isSuccess;
            TransactionId = transactionId;
            ErrorMessage = errorMessage;
        }
        
        public static PaymentResult Success(string transactionId) 
            => new PaymentResult(true, transactionId, null);
            
        public static PaymentResult Failed(string errorMessage) 
            => new PaymentResult(false, null, errorMessage);
    }
}

5. Parking Lot Aggregate Root

namespace ParkingLot.Domain.Aggregates
{
    public class ParkingLot : IAggregateRoot
    {
        private readonly List<Floor> _floors = new();
        private readonly Dictionary<string, ParkingTicket> _activeTickets = new();
        private readonly List<ParkingTicket> _ticketHistory = new();
        
        public int Id { get; }
        public string Name { get; }
        public IPricingStrategy PricingStrategy { get; private set; }
        public IReadOnlyList<Floor> Floors => _floors.AsReadOnly();
        public IReadOnlyDictionary<string, ParkingTicket> ActiveTickets => _activeTickets;
        
        private static int _nextId = 1;
        
        public ParkingLot(string name, IPricingStrategy pricingStrategy)
        {
            Id = _nextId++;
            Name = name;
            PricingStrategy = pricingStrategy;
        }
        
        public void AddFloor(int floorNumber, int compactSpots, int regularSpots, int largeSpots)
        {
            var floor = new Floor(floorNumber);
            
            int spotId = 1;
            for (int i = 1; i <= compactSpots; i++)
                floor.AddSpot(new ParkingSpot(spotId++, floorNumber, i, ParkingSpotType.Compact));
                
            for (int i = 1; i <= regularSpots; i++)
                floor.AddSpot(new ParkingSpot(spotId++, floorNumber, i, ParkingSpotType.Regular));
                
            for (int i = 1; i <= largeSpots; i++)
                floor.AddSpot(new ParkingSpot(spotId++, floorNumber, i, ParkingSpotType.Large));
                
            _floors.Add(floor);
        }
        
        public (ParkingSpot Spot, ParkingTicket Ticket) ParkVehicle(Vehicle vehicle)
        {
            // Find available spot
            var spot = FindAvailableSpot(vehicle);
            if (spot == null)
                throw new InvalidOperationException("No available parking spots");
                
            // Park the vehicle
            spot.ParkVehicle(vehicle);
            
            // Generate ticket
            var ticket = new ParkingTicket(
                vehicle.LicensePlate,
                vehicle.Type,
                spot.Id,
                spot.FloorId
            );
            
            _activeTickets[ticket.TicketNumber] = ticket;
            
            return (spot, ticket);
        }
        
        public ParkingTicket GetTicket(string ticketNumber)
        {
            if (_activeTickets.TryGetValue(ticketNumber, out var ticket))
                return ticket;
                
            return _ticketHistory.FirstOrDefault(t => t.TicketNumber == ticketNumber);
        }
        
        public Money CalculateCharge(string ticketNumber)
        {
            var ticket = GetTicket(ticketNumber);
            if (ticket == null)
                throw new ArgumentException($"Ticket {ticketNumber} not found");
                
            if (ticket.Status == TicketStatus.Exited)
                throw new InvalidOperationException("Ticket already exited");
                
            var duration = ticket.GetDuration();
            return PricingStrategy.CalculateCharge(duration, ticket.VehicleType);
        }
        
        public async Task<PaymentReceipt> ProcessPaymentAsync(
            string ticketNumber,
            PaymentMethod paymentMethod,
            IPaymentMethod paymentProcessor,
            Dictionary<string, string> paymentDetails)
        {
            var ticket = GetTicket(ticketNumber);
            if (ticket == null)
                throw new ArgumentException($"Ticket {ticketNumber} not found");
                
            if (ticket.Status != TicketStatus.Active)
                throw new InvalidOperationException($"Ticket is already {ticket.Status}");
                
            var amount = CalculateCharge(ticketNumber);
            
            var result = await paymentProcessor.ProcessPaymentAsync(amount, paymentDetails);
            
            if (!result.IsSuccess)
                throw new InvalidOperationException($"Payment failed: {result.ErrorMessage}");
                
            ticket.Pay(amount, paymentMethod, result.TransactionId);
            
            return new PaymentReceipt
            {
                TicketNumber = ticket.TicketNumber,
                Amount = amount,
                PaymentMethod = paymentMethod,
                TransactionId = result.TransactionId,
                PaidAt = DateTime.UtcNow
            };
        }
        
        public ParkingSpot ExitVehicle(string ticketNumber, out ParkingTicket ticket)
        {
            ticket = GetTicket(ticketNumber);
            if (ticket == null)
                throw new ArgumentException($"Ticket {ticketNumber} not found");
                
            if (ticket.Status != TicketStatus.Paid)
                throw new InvalidOperationException("Ticket must be paid before exit");
                
            // Find and vacate the spot
            var spot = FindSpotById(ticket.ParkingSpotId);
            if (spot == null)
                throw new InvalidOperationException($"Parking spot {ticket.ParkingSpotId} not found");
                
            spot.Vacate();
            
            ticket.Exit(DateTime.UtcNow);
            
            _activeTickets.Remove(ticketNumber);
            _ticketHistory.Add(ticket);
            
            return spot;
        }
        
        public int GetAvailableSpotsCount(ParkingSpotType? type = null)
        {
            return _floors.Sum(f => f.GetAvailableSpotsCount(type));
        }
        
        public Dictionary<int, Dictionary<ParkingSpotType, int>> GetAvailabilityByFloor()
        {
            var result = new Dictionary<int, Dictionary<ParkingSpotType, int>>();
            
            foreach (var floor in _floors)
            {
                result[floor.FloorNumber] = floor.GetAvailabilityByType();
            }
            
            return result;
        }
        
        public void ChangePricingStrategy(IPricingStrategy newStrategy)
        {
            PricingStrategy = newStrategy;
        }
        
        private ParkingSpot FindAvailableSpot(Vehicle vehicle)
        {
            var requiredType = vehicle.GetRequiredSpotType();
            
            foreach (var floor in _floors.OrderBy(f => f.FloorNumber))
            {
                var spot = floor.GetAvailableSpot(requiredType);
                if (spot != null)
                    return spot;
            }
            
            // Try larger spots if compact/regular not available
            if (requiredType == ParkingSpotType.Compact)
            {
                foreach (var floor in _floors)
                {
                    var spot = floor.GetAvailableSpot(ParkingSpotType.Regular);
                    if (spot != null) return spot;
                }
            }
            
            if (requiredType != ParkingSpotType.Large)
            {
                foreach (var floor in _floors)
                {
                    var spot = floor.GetAvailableSpot(ParkingSpotType.Large);
                    if (spot != null) return spot;
                }
            }
            
            return null;
        }
        
        private ParkingSpot FindSpotById(int spotId)
        {
            foreach (var floor in _floors)
            {
                var spot = floor.GetSpotById(spotId);
                if (spot != null)
                    return spot;
            }
            return null;
        }
    }
    
    public class Floor
    {
        private readonly List<ParkingSpot> _spots = new();
        
        public int FloorNumber { get; }
        public IReadOnlyList<ParkingSpot> Spots => _spots.AsReadOnly();
        
        public Floor(int floorNumber)
        {
            FloorNumber = floorNumber;
        }
        
        public void AddSpot(ParkingSpot spot)
        {
            _spots.Add(spot);
        }
        
        public ParkingSpot GetAvailableSpot(ParkingSpotType type)
        {
            return _spots.FirstOrDefault(s => s.Type == type && !s.IsOccupied);
        }
        
        public ParkingSpot GetSpotById(int id)
        {
            return _spots.FirstOrDefault(s => s.Id == id);
        }
        
        public int GetAvailableSpotsCount(ParkingSpotType? type = null)
        {
            if (type.HasValue)
                return _spots.Count(s => s.Type == type.Value && !s.IsOccupied);
                
            return _spots.Count(s => !s.IsOccupied);
        }
        
        public Dictionary<ParkingSpotType, int> GetAvailabilityByType()
        {
            return Enum.GetValues<ParkingSpotType>()
                .ToDictionary(t => t, t => _spots.Count(s => s.Type == t && !s.IsOccupied));
        }
    }
}

6. Repository Pattern

namespace ParkingLot.Domain.Repositories
{
    public interface IParkingLotRepository
    {
        Task<ParkingLot> GetByIdAsync(int id);
        Task<ParkingLot> GetDefaultAsync();
        Task SaveAsync(ParkingLot parkingLot);
        Task UpdateAsync(ParkingLot parkingLot);
    }
    
    public interface ITicketRepository
    {
        Task<ParkingTicket> GetByTicketNumberAsync(string ticketNumber);
        Task<IEnumerable<ParkingTicket>> GetByLicensePlateAsync(string licensePlate);
        Task<IEnumerable<ParkingTicket>> GetActiveTicketsAsync();
        Task AddAsync(ParkingTicket ticket);
        Task UpdateAsync(ParkingTicket ticket);
        Task<IEnumerable<ParkingTicket>> GetTicketsByDateRangeAsync(DateTime start, DateTime end);
    }
    
    public interface IPaymentReceiptRepository
    {
        Task AddAsync(PaymentReceipt receipt);
        Task<PaymentReceipt> GetByTicketNumberAsync(string ticketNumber);
        Task<IEnumerable<PaymentReceipt>> GetByDateRangeAsync(DateTime start, DateTime end);
    }
    
    // In-memory implementation for demo
    public class InMemoryParkingLotRepository : IParkingLotRepository
    {
        private static ParkingLot _defaultParkingLot;
        private static readonly object _lock = new();
        
        public Task<ParkingLot> GetByIdAsync(int id)
        {
            return Task.FromResult(_defaultParkingLot);
        }
        
        public Task<ParkingLot> GetDefaultAsync()
        {
            return Task.FromResult(_defaultParkingLot);
        }
        
        public Task SaveAsync(ParkingLot parkingLot)
        {
            lock (_lock)
            {
                _defaultParkingLot = parkingLot;
            }
            return Task.CompletedTask;
        }
        
        public Task UpdateAsync(ParkingLot parkingLot)
        {
            lock (_lock)
            {
                _defaultParkingLot = parkingLot;
            }
            return Task.CompletedTask;
        }
        
        public static void Initialize(ParkingLot parkingLot)
        {
            _defaultParkingLot = parkingLot;
        }
    }
}

7. Domain Services

namespace ParkingLot.Domain.Services
{
    public interface IParkingService
    {
        Task<EntryResult> EntryAsync(string licensePlate, VehicleType vehicleType);
        Task<ExitResult> ExitAsync(string ticketNumber);
        Task<PaymentReceipt> PayAsync(string ticketNumber, PaymentMethod method, Dictionary<string, string> paymentDetails);
        Task<Money> GetChargeAsync(string ticketNumber);
        Task<ParkingAvailability> GetAvailabilityAsync();
    }
    
    public class ParkingService : IParkingService
    {
        private readonly IParkingLotRepository _parkingLotRepository;
        private readonly ITicketRepository _ticketRepository;
        private readonly IPaymentReceiptRepository _receiptRepository;
        private readonly Dictionary<PaymentMethod, IPaymentMethod> _paymentProcessors;
        private readonly ILogger<ParkingService> _logger;
        
        public ParkingService(
            IParkingLotRepository parkingLotRepository,
            ITicketRepository ticketRepository,
            IPaymentReceiptRepository receiptRepository,
            IEnumerable<IPaymentMethod> paymentProcessors,
            ILogger<ParkingService> logger)
        {
            _parkingLotRepository = parkingLotRepository;
            _ticketRepository = ticketRepository;
            _receiptRepository = receiptRepository;
            _logger = logger;
            
            _paymentProcessors = paymentProcessors.ToDictionary(p => Enum.Parse<PaymentMethod>(p.MethodName));
        }
        
        public async Task<EntryResult> EntryAsync(string licensePlate, VehicleType vehicleType)
        {
            _logger.LogInformation("Processing entry for {LicensePlate} ({VehicleType})", licensePlate, vehicleType);
            
            var parkingLot = await _parkingLotRepository.GetDefaultAsync();
            var vehicle = VehicleFactory.Create(licensePlate, vehicleType);
            
            try
            {
                var (spot, ticket) = parkingLot.ParkVehicle(vehicle);
                
                await _ticketRepository.AddAsync(ticket);
                await _parkingLotRepository.UpdateAsync(parkingLot);
                
                _logger.LogInformation("Vehicle {LicensePlate} parked at spot {SpotId} on floor {FloorId}, ticket {TicketNumber}",
                    licensePlate, spot.Id, spot.FloorId, ticket.TicketNumber);
                
                return new EntryResult
                {
                    Success = true,
                    TicketNumber = ticket.TicketNumber,
                    ParkingSpot = spot.ToString(),
                    EntryTime = ticket.EntryTime,
                    Message = "Entry successful"
                };
            }
            catch (InvalidOperationException ex)
            {
                _logger.LogWarning(ex, "Entry failed for {LicensePlate}", licensePlate);
                return new EntryResult
                {
                    Success = false,
                    Message = ex.Message
                };
            }
        }
        
        public async Task<Money> GetChargeAsync(string ticketNumber)
        {
            var parkingLot = await _parkingLotRepository.GetDefaultAsync();
            return parkingLot.CalculateCharge(ticketNumber);
        }
        
        public async Task<PaymentReceipt> PayAsync(string ticketNumber, PaymentMethod method, Dictionary<string, string> paymentDetails)
        {
            _logger.LogInformation("Processing payment for ticket {TicketNumber} via {Method}", ticketNumber, method);
            
            var parkingLot = await _parkingLotRepository.GetDefaultAsync();
            
            if (!_paymentProcessors.TryGetValue(method, out var processor))
                throw new NotSupportedException($"Payment method {method} not supported");
            
            var receipt = await parkingLot.ProcessPaymentAsync(ticketNumber, method, processor, paymentDetails);
            
            await _receiptRepository.AddAsync(receipt);
            await _parkingLotRepository.UpdateAsync(parkingLot);
            
            _logger.LogInformation("Payment of {Amount} processed for ticket {TicketNumber}, transaction {TransactionId}",
                receipt.Amount, ticketNumber, receipt.TransactionId);
            
            return receipt;
        }
        
        public async Task<ExitResult> ExitAsync(string ticketNumber)
        {
            _logger.LogInformation("Processing exit for ticket {TicketNumber}", ticketNumber);
            
            var parkingLot = await _parkingLotRepository.GetDefaultAsync();
            
            try
            {
                var spot = parkingLot.ExitVehicle(ticketNumber, out var ticket);
                
                await _parkingLotRepository.UpdateAsync(parkingLot);
                await _ticketRepository.UpdateAsync(ticket);
                
                _logger.LogInformation("Vehicle {LicensePlate} exited after {Duration}, paid {Amount}",
                    ticket.LicensePlate, ticket.GetDuration(), ticket.AmountPaid);
                
                return new ExitResult
                {
                    Success = true,
                    LicensePlate = ticket.LicensePlate,
                    EntryTime = ticket.EntryTime,
                    ExitTime = ticket.ExitTime.Value,
                    Duration = ticket.GetDuration(),
                    AmountPaid = ticket.AmountPaid,
                    ParkingSpot = spot.ToString(),
                    Message = "Exit successful"
                };
            }
            catch (InvalidOperationException ex)
            {
                _logger.LogWarning(ex, "Exit failed for ticket {TicketNumber}", ticketNumber);
                return new ExitResult
                {
                    Success = false,
                    Message = ex.Message
                };
            }
        }
        
        public async Task<ParkingAvailability> GetAvailabilityAsync()
        {
            var parkingLot = await _parkingLotRepository.GetDefaultAsync();
            
            return new ParkingAvailability
            {
                TotalSpots = parkingLot.Floors.Sum(f => f.Spots.Count),
                AvailableSpots = parkingLot.GetAvailableSpotsCount(),
                AvailableByType = new Dictionary<string, int>
                {
                    ["Compact"] = parkingLot.GetAvailableSpotsCount(ParkingSpotType.Compact),
                    ["Regular"] = parkingLot.GetAvailableSpotsCount(ParkingSpotType.Regular),
                    ["Large"] = parkingLot.GetAvailableSpotsCount(ParkingSpotType.Large)
                },
                AvailabilityByFloor = parkingLot.GetAvailabilityByFloor()
            };
        }
    }
    
    public class EntryResult
    {
        public bool Success { get; set; }
        public string TicketNumber { get; set; }
        public string ParkingSpot { get; set; }
        public DateTime EntryTime { get; set; }
        public string Message { get; set; }
    }
    
    public class ExitResult
    {
        public bool Success { get; set; }
        public string LicensePlate { get; set; }
        public DateTime EntryTime { get; set; }
        public DateTime ExitTime { get; set; }
        public Duration Duration { get; set; }
        public Money AmountPaid { get; set; }
        public string ParkingSpot { get; set; }
        public string Message { get; set; }
    }
    
    public class PaymentReceipt
    {
        public string TicketNumber { get; set; }
        public Money Amount { get; set; }
        public PaymentMethod PaymentMethod { get; set; }
        public string TransactionId { get; set; }
        public DateTime PaidAt { get; set; }
        
        public override string ToString()
        {
            return $@"
╔══════════════════════════════════════════════════════════╗
║                    PARKING RECEIPT                       ║
╠══════════════════════════════════════════════════════════╣
║ Ticket: {TicketNumber,-46} ║
║ Amount: {Amount,-48} ║
║ Method: {PaymentMethod,-47} ║
║ Transaction: {TransactionId,-43} ║
║ Time: {PaidAt,-49} ║
╚══════════════════════════════════════════════════════════╝";
        }
    }
    
    public class ParkingAvailability
    {
        public int TotalSpots { get; set; }
        public int AvailableSpots { get; set; }
        public Dictionary<string, int> AvailableByType { get; set; }
        public Dictionary<int, Dictionary<ParkingSpotType, int>> AvailabilityByFloor { get; set; }
        
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendLine($"Total Spots: {TotalSpots}");
            sb.AppendLine($"Available Spots: {AvailableSpots}");
            sb.AppendLine("Available by Type:");
            foreach (var type in AvailableByType)
            {
                sb.AppendLine($"  {type.Key}: {type.Value}");
            }
            sb.AppendLine("Availability by Floor:");
            foreach (var floor in AvailabilityByFloor)
            {
                sb.AppendLine($"  Floor {floor.Key}:");
                foreach (var type in floor.Value)
                {
                    sb.AppendLine($"    {type.Key}: {type.Value}");
                }
            }
            return sb.ToString();
        }
    }
}

8. API Controllers

namespace ParkingLot.API.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ParkingController : ControllerBase
    {
        private readonly IParkingService _parkingService;
        private readonly ILogger<ParkingController> _logger;
        
        public ParkingController(IParkingService parkingService, ILogger<ParkingController> logger)
        {
            _parkingService = parkingService;
            _logger = logger;
        }
        
        [HttpGet("availability")]
        public async Task<IActionResult> GetAvailability()
        {
            var availability = await _parkingService.GetAvailabilityAsync();
            return Ok(availability);
        }
        
        [HttpPost("entry")]
        public async Task<IActionResult> Entry([FromBody] EntryRequest request)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
                
            var result = await _parkingService.EntryAsync(request.LicensePlate, request.VehicleType);
            
            if (!result.Success)
                return BadRequest(result);
                
            return Ok(result);
        }
        
        [HttpGet("tickets/{ticketNumber}/charge")]
        public async Task<IActionResult> GetCharge(string ticketNumber)
        {
            try
            {
                var charge = await _parkingService.GetChargeAsync(ticketNumber);
                return Ok(new { TicketNumber = ticketNumber, Amount = charge });
            }
            catch (ArgumentException ex)
            {
                return NotFound(ex.Message);
            }
            catch (InvalidOperationException ex)
            {
                return BadRequest(ex.Message);
            }
        }
        
        [HttpPost("tickets/{ticketNumber}/pay")]
        public async Task<IActionResult> Pay(string ticketNumber, [FromBody] PaymentRequest request)
        {
            try
            {
                var receipt = await _parkingService.PayAsync(ticketNumber, request.Method, request.PaymentDetails);
                return Ok(receipt);
            }
            catch (ArgumentException ex)
            {
                return NotFound(ex.Message);
            }
            catch (InvalidOperationException ex)
            {
                return BadRequest(ex.Message);
            }
        }
        
        [HttpPost("tickets/{ticketNumber}/exit")]
        public async Task<IActionResult> Exit(string ticketNumber)
        {
            var result = await _parkingService.ExitAsync(ticketNumber);
            
            if (!result.Success)
                return BadRequest(result);
                
            return Ok(result);
        }
    }
    
    public class EntryRequest
    {
        [Required]
        [MinLength(5)]
        [MaxLength(10)]
        public string LicensePlate { get; set; }
        
        [Required]
        public VehicleType VehicleType { get; set; }
    }
    
    public class PaymentRequest
    {
        [Required]
        public PaymentMethod Method { get; set; }
        
        public Dictionary<string, string> PaymentDetails { get; set; }
    }
}

9. Dependency Injection Setup

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register repositories
builder.Services.AddSingleton<IParkingLotRepository, InMemoryParkingLotRepository>();
builder.Services.AddSingleton<ITicketRepository, InMemoryTicketRepository>();
builder.Services.AddSingleton<IPaymentReceiptRepository, InMemoryPaymentReceiptRepository>();

// Register payment methods
builder.Services.AddScoped<IPaymentMethod, CashPaymentMethod>();
builder.Services.AddScoped<IPaymentMethod, CreditCardPaymentMethod>();
builder.Services.AddScoped<IPaymentMethod, MobilePaymentMethod>();

// Register domain services
builder.Services.AddScoped<IParkingService, ParkingService>();

// Configure pricing strategy
builder.Services.AddSingleton<IPricingStrategy>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    var strategy = config["PricingStrategy"] ?? "Hourly";
    
    IPricingStrategy baseStrategy = strategy switch
    {
        "FlatRate" => new FlatRatePricingStrategy(),
        "EarlyBird" => new EarlyBirdPricingStrategy(),
        _ => new HourlyPricingStrategy()
    };
    
    // Apply weekend surcharge if configured
    if (config.GetValue<bool>("WeekendSurcharge", false))
    {
        return new WeekendPricingStrategy(baseStrategy);
    }
    
    return baseStrategy;
});

// Initialize parking lot
var parkingLot = new ParkingLot("Central Parking", builder.Services.BuildServiceProvider().GetRequiredService<IPricingStrategy>());
parkingLot.AddFloor(1, compactSpots: 20, regularSpots: 50, largeSpots: 10);
parkingLot.AddFloor(2, compactSpots: 15, regularSpots: 40, largeSpots: 8);
parkingLot.AddFloor(3, compactSpots: 10, regularSpots: 30, largeSpots: 5);

InMemoryParkingLotRepository.Initialize(parkingLot);

var app = builder.Build();

// Configure pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

10. Concurrency Handling

namespace ParkingLot.Infrastructure.Concurrency
{
    public class ParkingSpotReservationService
    {
        private readonly SemaphoreSlim _semaphore;
        private readonly ConcurrentDictionary<int, string> _reservations;
        
        public ParkingSpotReservationService(int maxConcurrentOperations = 10)
        {
            _semaphore = new SemaphoreSlim(maxConcurrentOperations);
            _reservations = new ConcurrentDictionary<int, string>();
        }
        
        public async Task<bool> TryReserveSpotAsync(int spotId, string ticketNumber, TimeSpan timeout)
        {
            // Acquire semaphore to limit concurrent operations
            if (!await _semaphore.WaitAsync(timeout))
                return false;
                
            try
            {
                // Try to reserve the spot
                return _reservations.TryAdd(spotId, ticketNumber);
            }
            finally
            {
                _semaphore.Release();
            }
        }
        
        public bool ReleaseSpot(int spotId, string ticketNumber)
        {
            return _reservations.TryRemove(spotId, out var reservedFor) && reservedFor == ticketNumber;
        }
        
        public bool IsSpotReserved(int spotId)
        {
            return _reservations.ContainsKey(spotId);
        }
        
        public string GetReservationTicket(int spotId)
        {
            return _reservations.TryGetValue(spotId, out var ticket) ? ticket : null;
        }
    }
    
    // Optimistic concurrency with versioning
    public class ParkingSpotWithVersion : ParkingSpot
    {
        public int Version { get; private set; }
        
        public ParkingSpotWithVersion(int id, int floorId, int spotNumber, ParkingSpotType type) 
            : base(id, floorId, spotNumber, type)
        {
            Version = 1;
        }
        
        public new void ParkVehicle(Vehicle vehicle)
        {
            base.ParkVehicle(vehicle);
            Version++;
        }
        
        public new void Vacate()
        {
            base.Vacate();
            Version++;
        }
        
        public bool TryUpdate(Action updateAction, int expectedVersion)
        {
            if (Version != expectedVersion)
                return false;
                
            updateAction();
            Version++;
            return true;
        }
    }
}

11. Unit Tests

namespace ParkingLot.Tests
{
    public class ParkingLotTests
    {
        [Fact]
        public void ParkVehicle_WhenSpotAvailable_ShouldGenerateTicket()
        {
            // Arrange
            var pricingStrategy = new HourlyPricingStrategy();
            var parkingLot = new ParkingLot("Test Lot", pricingStrategy);
            parkingLot.AddFloor(1, compactSpots: 1, regularSpots: 0, largeSpots: 0);
            
            var vehicle = new Car("ABC123");
            
            // Act
            var (spot, ticket) = parkingLot.ParkVehicle(vehicle);
            
            // Assert
            Assert.NotNull(ticket);
            Assert.NotNull(ticket.TicketNumber);
            Assert.Equal(TicketStatus.Active, ticket.Status);
            Assert.True(spot.IsOccupied);
        }
        
        [Fact]
        public void ParkVehicle_WhenNoSpotsAvailable_ShouldThrowException()
        {
            // Arrange
            var pricingStrategy = new HourlyPricingStrategy();
            var parkingLot = new ParkingLot("Test Lot", pricingStrategy);
            parkingLot.AddFloor(1, compactSpots: 0, regularSpots: 0, largeSpots: 0);
            
            var vehicle = new Car("ABC123");
            
            // Act & Assert
            Assert.Throws<InvalidOperationException>(() => parkingLot.ParkVehicle(vehicle));
        }
        
        [Fact]
        public void CalculateCharge_ForOneHour_ShouldReturnCorrectAmount()
        {
            // Arrange
            var pricingStrategy = new HourlyPricingStrategy();
            var parkingLot = new ParkingLot("Test Lot", pricingStrategy);
            parkingLot.AddFloor(1, compactSpots: 1, regularSpots: 0, largeSpots: 0);
            
            var vehicle = new Car("ABC123");
            var (_, ticket) = parkingLot.ParkVehicle(vehicle);
            
            // Simulate time passing
            var duration = Duration.FromDateTime(ticket.EntryTime, ticket.EntryTime.AddHours(1.5));
            
            // Act
            var charge = pricingStrategy.CalculateCharge(duration, vehicle.Type);
            
            // Assert
            Assert.Equal(5.00m, charge.Amount); // 2.50 * 2 hours (ceil)
        }
        
        [Theory]
        [InlineData("ABC123", VehicleType.Car, true)]
        [InlineData("XYZ789", VehicleType.Motorcycle, true)]
        [InlineData("TRUCK1", VehicleType.Truck, true)]
        public void ParkVehicle_WithDifferentVehicleTypes_ShouldWork(string plate, VehicleType type, bool shouldSucceed)
        {
            // Arrange
            var pricingStrategy = new HourlyPricingStrategy();
            var parkingLot = new ParkingLot("Test Lot", pricingStrategy);
            parkingLot.AddFloor(1, compactSpots: 5, regularSpots: 5, largeSpots: 5);
            
            var vehicle = VehicleFactory.Create(plate, type);
            
            // Act
            var exception = Record.Exception(() => parkingLot.ParkVehicle(vehicle));
            
            // Assert
            if (shouldSucceed)
                Assert.Null(exception);
            else
                Assert.NotNull(exception);
        }
    }
    
    public class PricingStrategyTests
    {
        [Fact]
        public void HourlyPricing_ForMotorcycle_ShouldCalculateCorrectly()
        {
            var strategy = new HourlyPricingStrategy();
            var duration = Duration.FromDateTime(DateTime.Now, DateTime.Now.AddHours(2.5));
            
            var charge = strategy.CalculateCharge(duration, VehicleType.Motorcycle);
            
            Assert.Equal(3.00m, charge.Amount); // 1.00 * 3 hours
        }
        
        [Fact]
        public void FlatRatePricing_ForCar_ShouldCalculateCorrectly()
        {
            var strategy = new FlatRatePricingStrategy();
            var duration = Duration.FromDateTime(DateTime.Now, DateTime.Now.AddHours(3));
            
            var charge = strategy.CalculateCharge(duration, VehicleType.Car);
            
            Assert.Equal(10.00m, charge.Amount);
        }
        
        [Fact]
        public void FlatRatePricing_ForCar_FirstHourFree()
        {
            var strategy = new FlatRatePricingStrategy();
            var duration = Duration.FromDateTime(DateTime.Now, DateTime.Now.AddMinutes(45));
            
            var charge = strategy.CalculateCharge(duration, VehicleType.Car);
            
            Assert.Equal(0m, charge.Amount);
        }
    }
}

12. Summary

In this case study, we've built a complete Parking Lot System applying:

  • Domain-Driven Design - Entities, Value Objects, Aggregates, Repositories
  • Strategy Pattern - For pricing strategies and payment methods
  • Factory Pattern - For creating vehicles
  • Repository Pattern - For data access abstraction
  • Domain Events - For ticket lifecycle events
  • SOLID Principles - Single responsibility, open/closed, dependency inversion
  • Concurrency Handling - Semaphores, optimistic locking
  • Unit Testing - Comprehensive test coverage

13. Practice Extensions

Try extending this system with:

  1. Reservation System - Allow customers to reserve spots in advance
  2. Loyalty Program - Discounts for frequent parkers
  3. License Plate Recognition - Automatic entry/exit
  4. Mobile App Integration - Find and reserve spots via mobile
  5. Dynamic Pricing - Prices based on demand and time of day
  6. Electric Vehicle Charging - Special spots with charging stations
  7. Monthly Pass - Subscription model for regular customers

📝 Key Takeaways:

  • Start with requirements analysis and domain language
  • Model entities and value objects based on business concepts
  • Use aggregates to maintain consistency boundaries
  • Apply appropriate design patterns for flexibility
  • Handle concurrency carefully in real-world systems
  • Write comprehensive unit tests for core logic

Happy Coding! 🚀

Post a Comment

Feel free to ask your query...
Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.