Skip to content

System Architecture

1. Architectural Pattern

OLOW Hotel ERP follows a Modular Monolith architecture using Domain-Driven Design (DDD) principles.

Directory Structure

Instead of the default Laravel structure (controllers in one folder, models in another), we group code by Business Domain.

app/
├── Domain/
│   ├── Accounting/          <-- Module Name
│   │   ├── Models/
│   │   ├── Services/
│   │   ├── Enums/
│   │   ├── Events/
│   │   └── Listeners/
│   ├── FrontDesk/
│   ├── Restaurant/
│   └── ... (21 domains total)
├── Livewire/
│   ├── Admin/               <-- Admin panel components
│   ├── Guest/               <-- Guest portal components
│   ├── Marketing/           <-- Public marketing pages
│   ├── Accounting/          <-- Module-specific components
│   └── ...
├── Http/
│   ├── Controllers/         <-- Thin controllers (API + traditional)
│   └── Middleware/
├── Concerns/                <-- Shared traits (BelongsToProperty, etc.)
└── Providers/               <-- Service Providers

2. Request Lifecycle

  1. Entry: Request hits public/index.php.
  2. Tenant Resolution: IdentifyTenant middleware runs early.
    • Checks Host header for subdomain (e.g., hotel-a.hotels.test).
    • Finds Property record by subdomain or custom domain.
    • Binds the property: app()->instance('current_property_id', $property->id).
    • Property::currentId() is used throughout the app to retrieve the current tenant.
  3. Guard Selection: The route group determines which auth guard applies:
    • Marketing routes (main domain) — no auth
    • Guest routes (tenant subdomain) — customer guard
    • Admin routes (/admin/*) — web guard
    • Super Admin routes (/platform/*) — admin guard
  4. Routing: Dispatched via routes/web.php, routes/admin.php, and 19 module route files in routes/modules/.
  5. Controller/Component: Application logic executes.
    • Read: Queries Models (automatically scoped by BelongsToProperty trait).
    • Write: Calls Domain Services (e.g., BookingService).
  6. Service Layer:
    • Validates business rules.
    • Uses AccountingMapService to resolve GL accounts dynamically.
    • Wraps database operations in Transactions (DB::transaction).
    • Dispatches Events (BookingCreated).
  7. Response: View/JSON returned to user.

3. Database Design

Single Database

All tenants share the same MySQL database.

  • Pros: Easy migration, simple backups, cross-tenant reporting (Super Admin).
  • Cons: Requires strict code discipline to prevent data leaks.

Tenant Isolation

  • Column: Every tenant-aware table has property_id (BigInt).
  • Trait: App\Concerns\BelongsToProperty handles:
    • Auto-scoping all queries to Property::currentId() via a global scope
    • Auto-setting property_id on record creation
    • Throwing RuntimeException if property_id is missing during creation
  • Service Contracts: Critical services enforce explicit property_id passing.

4. Multi-Portal Architecture

The system serves four distinct portals:

PortalDomain / PathGuardLayout
MarketingMain domain (hotels.test)Nonelayouts.marketing
Guest PortalTenant subdomain (hotel.hotels.test)customerlayouts.guest / layouts.guest-auth
Admin PanelTenant subdomain + /adminweblayouts.admin
Super AdminTenant subdomain + /platformadminlayouts.admin

5. Key Design Patterns

Repository Pattern (Avoided)

We generally avoid Repositories in favor of:

  • Eloquent Models (Active Record) for simple reads/writes.
  • Domain Services for complex business logic.
  • Action Classes for single-purpose CLI/Job tasks.

Event-Driven Architecture

We use Laravel Events to decouple modules.

  • Example: BookingCheckedOut
  • Listener A: SendThankYouSMS (SMS Module).
  • Listener B: AwardLoyaltyPoints (Wallet Module).
  • Listener C: CreateHousekeepingTask (Housekeeping Module).
  • Benefit: FrontDesk module doesn't need to know about Housekeeping internals.

Service-Oriented (Internal)

Modules communicate via public Service methods, not by raw DB queries into other domains.

  • Good: AccountingPoster::createEntry(...)
  • Bad: JournalEntry::create(...) called from FrontDesk.

AccountingMapService Pattern

All financial modules resolve GL accounts via AccountingMapService + AccountingKey enum instead of hardcoding account IDs. This allows per-property Chart of Accounts customization.

6. Security

  • Authentication: Laravel Fortify with triple-guard system (web, customer, admin).
  • Authorization: Role-Based Access Control (RBAC) via spatie/laravel-permission.
  • Impersonation: lab404/laravel-impersonate for admin support workflows.
  • Validation: FormRequests for HTTP input; strict typing for Domain input.
  • Real-Time: Laravel Reverb WebSocket server for live notifications.