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 Providers2. Request Lifecycle
- Entry: Request hits
public/index.php. - Tenant Resolution:
IdentifyTenantmiddleware runs early.- Checks
Hostheader for subdomain (e.g.,hotel-a.hotels.test). - Finds
Propertyrecord 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.
- Checks
- Guard Selection: The route group determines which auth guard applies:
- Marketing routes (main domain) — no auth
- Guest routes (tenant subdomain) —
customerguard - Admin routes (
/admin/*) —webguard - Super Admin routes (
/platform/*) —adminguard
- Routing: Dispatched via
routes/web.php,routes/admin.php, and 19 module route files inroutes/modules/. - Controller/Component: Application logic executes.
- Read: Queries Models (automatically scoped by
BelongsToPropertytrait). - Write: Calls Domain Services (e.g.,
BookingService).
- Read: Queries Models (automatically scoped by
- Service Layer:
- Validates business rules.
- Uses
AccountingMapServiceto resolve GL accounts dynamically. - Wraps database operations in Transactions (
DB::transaction). - Dispatches Events (
BookingCreated).
- 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\BelongsToPropertyhandles:- Auto-scoping all queries to
Property::currentId()via a global scope - Auto-setting
property_idon record creation - Throwing
RuntimeExceptionifproperty_idis missing during creation
- Auto-scoping all queries to
- Service Contracts: Critical services enforce explicit
property_idpassing.
4. Multi-Portal Architecture
The system serves four distinct portals:
| Portal | Domain / Path | Guard | Layout |
|---|---|---|---|
| Marketing | Main domain (hotels.test) | None | layouts.marketing |
| Guest Portal | Tenant subdomain (hotel.hotels.test) | customer | layouts.guest / layouts.guest-auth |
| Admin Panel | Tenant subdomain + /admin | web | layouts.admin |
| Super Admin | Tenant subdomain + /platform | admin | layouts.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-impersonatefor admin support workflows. - Validation: FormRequests for HTTP input; strict typing for Domain input.
- Real-Time: Laravel Reverb WebSocket server for live notifications.