A production-ready hotel search API that aggregates results from multiple suppliers with parallel execution, deduplication, and comprehensive filtering. Built with Laravel 12 following enterprise-grade architecture principles.
This API solves the problem of searching hotels across multiple suppliers by:
- Fetching data from 4+ suppliers simultaneously
- Normalizing different data formats into a unified structure
- Removing duplicates and selecting the best price
- Providing advanced filtering and sorting capabilities
- Handling failures gracefully without breaking the user experience
- Single Responsibility: Each service handles one specific concern
- Open/Closed: Easy to add new suppliers without modifying existing code
- Liskov Substitution: All suppliers implement the same contract
- Interface Segregation: Clean, focused interfaces
- Dependency Inversion: Services depend on abstractions via DI container
- DRY: Shared logic centralized in abstract classes
- KISS: Simple, straightforward implementation
- Testable: Proper dependency injection and mocking support
- PHP 8.2+
- Composer
- Laravel 12
- SQLite (for development) or MySQL/PostgreSQL (for production)
- Clone and Install Dependencies
git clone https://github.com/hassangomaa/ctx-multi-supplier-hotels
cd ctx-multi-supplier-hotels
composer install
- Environment Setup
cp .env.example .env
php artisan key:generate
- Database Setup
# For MySQL/PostgreSQL (production)
# Update .env with your database credentials
php artisan migrate
- Start Development Server
php artisan serve
- Verify Installation
# Test the API endpoint
curl "http://localhost:8000/api/hotels/search?location=Dubai&check_in=2025-01-15&check_out=2025-01-20"
Create your .env
file with these key variables:
# Application
APP_NAME="CTX Hotel Search API"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8000
# Database
DB_CONNECTION=sqlite
DB_DATABASE=database/database.sqlite
# Supplier Configuration
SUPPLIER_TIMEOUT=3
SUPPLIER_CONCURRENCY=4
# Development Mock Suppliers
SUPPLIER_A_BASE_URL=/mock/supplier-a
SUPPLIER_B_BASE_URL=/mock/supplier-b
SUPPLIER_C_BASE_URL=/mock/supplier-c
SUPPLIER_D_BASE_URL=/mock/supplier-d
# Production External Suppliers (uncomment when ready)
# SUPPLIER_A_BASE_URL=https://api.supplier-a.com/hotels
# SUPPLIER_B_BASE_URL=https://api.supplier-b.com/search
# SUPPLIER_C_BASE_URL=https://api.supplier-c.com/availability
# SUPPLIER_D_BASE_URL=https://api.supplier-d.com/rooms
Edit config/suppliers.php
to:
- Enable/disable specific suppliers
- Set request timeouts
- Configure concurrency limits
- Define base URLs for each supplier
Endpoint: GET /api/hotels/search
Parameter | Type | Description | Example |
---|---|---|---|
location |
string | City or country | Dubai, UAE |
check_in |
date | Arrival date | 2025-01-15 |
check_out |
date | Departure date | 2025-01-20 |
Parameter | Type | Description | Default |
---|---|---|---|
guests |
integer | Number of guests | 1 |
min_price |
numeric | Minimum price per night | null |
max_price |
numeric | Maximum price per night | null |
sort_by |
string | Sort order: price or rating |
null |
lat |
numeric | Latitude for location filtering | null |
lng |
numeric | Longitude for location filtering | null |
radius_km |
numeric | Search radius in kilometers | null |
Basic Search:
curl "http://localhost:8000/api/hotels/search?location=Dubai&check_in=2025-01-15&check_out=2025-01-20"
Advanced Search with Filters:
curl "http://localhost:8000/api/hotels/search?location=Dubai&check_in=2025-01-15&check_out=2025-01-20&guests=2&min_price=100&max_price=500&sort_by=price&lat=25.2048&lng=55.2708&radius_km=10"
Language-Specific Search:
# English
curl "http://localhost:8000/api/hotels/search?location=Dubai&check_in=2025-01-15&check_out=2025-01-20&lang=en"
# Arabic
curl "http://localhost:8000/api/hotels/search?location=Dubai&check_in=2025-01-15&check_out=2025-01-20&lang=ar"
{
"success": true,
"msg": "Hotels retrieved successfully",
"data": [
{
"name": "Grand City Hotel",
"location": "Dubai, UAE",
"price_per_night": 240.0,
"available_rooms": 3,
"rating": 4.5,
"source": "supplier_b"
}
]
}
For development and testing, these endpoints provide sample data:
GET /mock/supplier-a
- Sample hotel data from Supplier AGET /mock/supplier-b
- Sample hotel data from Supplier BGET /mock/supplier-c
- Sample hotel data from Supplier CGET /mock/supplier-d
- Sample hotel data from Supplier D
Service | Purpose | Key Features |
---|---|---|
HotelSearchService |
Main orchestrator | Parallel execution, deduplication, coordination |
AbstractSupplierService |
Base supplier class | HTTP handling, error management, parameter building |
HotelNormalizer |
Data standardization | Format conversion, field mapping |
HotelFilter |
Business logic filtering | Price, location, availability, coordinates |
Supplier | Extends | Configuration Key |
---|---|---|
SupplierAService |
AbstractSupplierService |
supplier_a |
SupplierBService |
AbstractSupplierService |
supplier_b |
SupplierCService |
AbstractSupplierService |
supplier_c |
SupplierDService |
AbstractSupplierService |
supplier_d |
Middleware | Purpose | Applied To |
---|---|---|
XssMiddleware |
XSS protection | All API routes |
SetLocale |
Language detection | All routes |
- Technology: Laravel's
Http::pool()
for concurrent requests - Benefits: Reduced total response time, configurable timeouts
- Configuration: Adjustable concurrency limits via environment variables
- Algorithm: Groups hotels by name + location (case-insensitive)
- Selection: Keeps the lowest price option when duplicates exist
- Benefits: Better user experience, no redundant results
- Price Range: Min/max price filtering
- Location: Coordinate-based filtering with optional radius
- Availability: Guest count validation
- Performance: Efficient collection operations
- Input: Handles different supplier data formats
- Output: Consistent structure across all suppliers
- Flexibility: Easy to add new supplier formats
- Graceful Degradation: Supplier failures don't break the response
- Comprehensive Logging: Detailed error tracking for debugging
- User Experience: Partial results when some suppliers fail
- Languages: English (en) and Arabic (ar) support
- Detection: Query parameter or Accept-Language header
- Fallback: Graceful language fallback handling
- Mock Suppliers: ~50-100ms (local processing)
- External Suppliers: ~200-500ms (network + processing)
- Parallel Execution: Total time = slowest supplier response
- Concurrency: Configurable supplier limits
- Memory: Efficient collection operations
- Caching: Ready for Redis/Memcached integration
- Logging: Comprehensive supplier interaction logging
- Metrics: Response time tracking ready
- Alerts: Error threshold monitoring ready
- Request Validation: Comprehensive parameter validation
- Type Safety: Strict type checking for all inputs
- Sanitization: XSS protection middleware
- No Sensitive Data: No API keys or credentials exposed
- Rate Limiting: Ready for implementation
- CORS: Configurable cross-origin policies
-
Environment Configuration
- Set
APP_ENV=production
- Disable
APP_DEBUG
- Configure production database
- Set external supplier URLs
- Set
-
Performance Optimization
- Enable caching (Redis/Memcached)
- Configure queue workers
- Set appropriate timeouts
-
Monitoring Setup
- Application logging
- Performance metrics
- Error alerting
- Traditional: VPS/Cloud server with Nginx/Apache
- Container: Docker with Docker Compose
- Cloud: AWS, Google Cloud, Azure
- Platform: Laravel Forge, Vercel, Heroku
- Create Service Class
<?php
namespace App\Services\Suppliers;
class NewSupplierService extends AbstractSupplierService
{
protected function configKey(): string
{
return 'new_supplier';
}
protected function name(): string
{
return 'new_supplier';
}
}
- Add Configuration
// config/suppliers.php
'new_supplier' => [
'enabled' => true,
'base_url' => env('NEW_SUPPLIER_BASE_URL', '/mock/new-supplier'),
],
- Register Service
// app/Providers/AppServiceProvider.php
$this->app->bind('supplier.new', NewSupplierService::class);
- Add Tests
// tests/Feature/NewSupplierTest.php
// Test the new supplier integration
Issue | Cause | Solution |
---|---|---|
Supplier timeout errors | Network issues or slow responses | Increase SUPPLIER_TIMEOUT value |
Empty results | All suppliers failing | Check supplier URLs and network connectivity |
Validation errors | Invalid parameter format | Verify date formats and parameter types |
Memory issues | Large result sets | Implement pagination or result limiting |
Enable debug mode in .env
:
APP_DEBUG=true
LOG_LEVEL=debug
Check logs in storage/logs/laravel.log
for detailed error information.
The API is ready for OpenAPI documentation generation. Use tools like:
- Laravel Scribe
- L5-Swagger
- OpenAPI Generator
Create a Postman collection with the provided examples for easy testing and development.
- Fork the repository
- Create a feature branch
- Implement changes following SOLID principles
- Add comprehensive tests
- Submit a pull request
- Follow PSR-12 coding standards
- Maintain SOLID principles
- Write meaningful commit messages
- Include tests for new features
This project is licensed under the MIT License - see the LICENSE file for details.
For technical support or questions:
- Create an issue in the repository
- Check the troubleshooting section
- Review the code examples and tests
Built with β€οΈ using Laravel 12 and modern PHP practices