class ReservationService {

  public function calculateTotal()
  {
      return $this->pricingService
          ->applySeasonModifier()
          ->applyDiscountRules()
          ->applyChannelCommission()
          ->applyTaxes()
          ->buildInvoice();
  }

}

// Domain rules evolve
// architecture decisions matter
// complexity grows over time

Back to projects

Architecture Comparison: Reservation System

4 architectures. 4 phases of growing complexity. Same domain.

PHP 8.2 Laravel 12 4 Architectures 4 Phases 78 Tests 506 Assertions
View on GitHub

The Experiment

This is a practical laboratory exploring how different architectural approaches respond to increasing domain complexity. The same business logic is implemented in four distinct ways, each tackling the same problem with different design philosophies.

How does your architecture behave when the domain becomes complex?

Domain & Endpoints

Hotel Reservation System with progressive business rule complexity across 4 phases.

Shared Endpoints

Method Endpoint Description
POST /api/{arch}/v{phase}/reservation Create reservation
GET /api/{arch}/v{phase}/reservation/{id} Get reservation

URL Prefixes by Architecture

URL Prefix Architecture
/api/arch_01/ Monolithic Eloquent
/api/arch_02/ Repository Pattern
/api/arch_03/ Strategy + Polymorphism
/api/arch_04/ Decorator Domain

Evolution Phases

Domain complexity increases progressively across 4 phases

Phase 01: Base Calculation

Price per night, per_night/per_stay extras

Phase 02: Conditional Rules

Volume discounts (7/14 nights), combined promo, minimum price, spa validation

Phase 03: Polymorphic Behavior

Hotel/event taxes, commissions, 3-night event restriction

Phase 04: Combinable Rules

Early booking (30/60 days), seasonal surcharge (high/low season)

Architecture Trade-off Map

Where does each architecture live in the simplicity vs fragility space? The ideal is bottom-left (simple AND robust), but reality forces trade-offs.

Where does each architecture live in the simplicity vs fragility space? The ideal is bottom-left (simple AND robust), but reality forces trade-offs.

🏆 Final Rankings

Criterion 🥇 Gold 🥈 Silver 🥉 Bronze ❌ Worst
Simplicity A01 (6 files) A02 (10) A03 (14) A04 (22)
Thin Controller A02/A03/A04 (30-32) A01 (184)
Thin Service A04 (99+152) A03 (123) A02 (172) A01 (184)
Testability A04 (pure domain) A03 (strategy) A02 (service) A01 (needs DB)
Extensibility A04 (new Decorator) A03 (new Strategy) A02 (modify service) A01 (modify controller)
Fragility A04 (low) A03 (medium) A02 (medium-high) A01 (high)

⚡ When to Use Each

Architecture selection based on your domain characteristics

Stable domain, no changing rules

→ A01 or A02

Product types with different formulas

→ A03

Pricing rules that change frequently

→ A04

💡 Key Conclusions

A01: Simplicity has exponential cost

Controller grew from 83 to 184 lines in a single method handling 13 responsibilities. Early booking discount amount is parsed from the discount_reason string (str_starts_with + explode) — a fragile hack.

A02: God Service problem

Controller stays at 30 lines but all complexity moved to a 172-line 'God Service'. Repository Pattern decouples infrastructure but doesn't reduce algorithmic complexity.

A03: Strategy duplication

Phase04PricingStrategy duplicates all logic from BasicPricingStrategy just to add 3 methods. Good balance but needs refactoring to avoid strategy duplication per phase.

A04: Horizontal scalability

Highest initial cost (22 files, 1139 lines) but scales horizontally: each new rule is a new Decorator file, not modified code. Builder + Decorator means Phase 05 barely touches existing code.

Ready to dive deeper?