Skip to content

MacumbaTravel Architecture Guide

Introduction

This guide explains the architecture, development paradigms, and design patterns used in the MacumbaTravel backend. It's designed to help junior engineers understand how the system works and why certain architectural decisions were made.

System Overview

MacumbaTravel is a budget-first travel planning application that uses AI to provide personalized travel recommendations based on user preferences, budget constraints, and other parameters. The system consists of:

  1. FastAPI Backend: The core API that handles requests, business logic, and data access
  2. PostgreSQL Database: Stores user data, travel information, and application state
  3. PostgreSQL Cache: Cost-effective caching using PostgreSQL (saves ~$110/month vs Redis)
  4. AI Services: External AI providers (Claude as primary, Gemini as fallback) for generating travel recommendations
  5. External APIs: Integration with flight pricing, weather, and image services
  6. Circuit Breakers: Resilience patterns for external service failures
  7. Progressive Enhancement: Status tracking for enrichment processes

Architecture Diagram

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│                 │     │                 │     │                 │
│   Client Apps   │────▶│  FastAPI API    │────▶│   PostgreSQL    │
│                 │     │                 │     │  (Data + Cache) │
└─────────────────┘     └────────┬────────┘     └─────────────────┘
                 ┌───────────────┼───────────────┐
                 │               │               │
                 ▼               ▼               ▼
        ┌─────────────┐  ┌─────────────┐  ┌─────────────┐
        │             │  │             │  │             │
        │ PostgreSQL  │  │  AI         │  │  External   │
        │ Cache Impl  │  │  Services   │  │  APIs       │
        │             │  │             │  │             │
        └─────────────┘  └─────────────┘  └─────────────┘

Core Development Paradigms

1. Domain-Driven Design (DDD)

MacumbaTravel follows Domain-Driven Design principles to organize code around the business domain:

  • Domain Models: Core business entities (User, Recommendation, Destination)
  • Services: Business logic encapsulated in service classes
  • Repositories: Data access abstraction
  • Value Objects: Immutable objects representing concepts with no identity

This approach helps maintain a clear separation between business logic and infrastructure concerns.

Why it's a good fit:

  • Travel planning has a rich domain model with complex business rules
  • DDD helps manage complexity as the application grows
  • Facilitates communication between developers and domain experts

2. Clean Architecture

The codebase follows Clean Architecture principles with distinct layers:

  • API Layer: FastAPI routes and controllers
  • Service Layer: Business logic and orchestration
  • Domain Layer: Core business entities and rules
  • Infrastructure Layer: External dependencies (database, cache, AI providers)

Dependencies flow inward, with inner layers having no knowledge of outer layers.

Why it's a good fit:

  • Promotes testability by allowing components to be tested in isolation
  • Facilitates changes to external dependencies without affecting core business logic
  • Provides clear boundaries between different parts of the system

3. Dependency Injection

MacumbaTravel extensively uses FastAPI's dependency injection system to:

  • Manage service lifecycles
  • Provide access to resources (database, cache, external services)
  • Support testability through dependency substitution

Why it's a good fit:

  • Reduces coupling between components
  • Simplifies testing by allowing dependencies to be mocked
  • Centralizes configuration and resource management

4. Asynchronous Programming

The application uses Python's asynchronous features (async/await) throughout the entire stack:

  • Database Operations: All SQLAlchemy operations use async sessions and queries
  • HTTP Requests: Non-blocking I/O operations for external API calls
  • AI Service Calls: Concurrent processing of AI recommendation generation
  • Request Handling: Efficient handling of multiple concurrent requests

Implementation Details:

  • All database models use AsyncSession for non-blocking database operations
  • Repository pattern implemented with async methods
  • Service layer uses async/await for all I/O operations
  • FastAPI endpoints are fully async-compatible

Why it's a good fit:

  • Significantly improves performance for I/O-bound operations
  • Allows the server to handle more concurrent requests efficiently
  • Reduces resource consumption and improves scalability
  • Well-supported by FastAPI and modern Python ecosystem

5. CQRS-Inspired Approach

While not a strict implementation of Command Query Responsibility Segregation (CQRS), the codebase separates:

  • Commands: Operations that change state (create user, save recommendation)
  • Queries: Operations that retrieve data (get recommendations, search destinations)

Why it's a good fit:

  • Simplifies complex domain models
  • Allows for different optimization strategies for reads and writes
  • Supports future scalability (e.g., read replicas for queries)

Key Design Patterns

1. Repository Pattern

Used to abstract data access logic:

class UserRepository:
    def __init__(self, db: Session):
        self.db = db

    async def get_by_id(self, user_id: int) -> Optional[User]:
        return self.db.query(User).filter(User.id == user_id).first()

2. Factory Pattern

Used to create complex objects:

def create_ai_provider(provider_type: str) -> AIProvider:
    if provider_type == "claude":
        return ClaudeProvider()
    elif provider_type == "gemini":
        return GeminiProvider()
    else:
        raise ValueError(f"Unsupported provider: {provider_type}")

3. Strategy Pattern

Used for interchangeable algorithms:

class AIService:
    def __init__(self, primary_provider: AIProvider, fallback_provider: Optional[AIProvider] = None):
        self.primary_provider = primary_provider
        self.fallback_provider = fallback_provider

    async def generate_recommendation(self, prompt: str) -> str:
        try:
            return await self.primary_provider.generate(prompt)
        except Exception as e:
            if self.fallback_provider:
                return await self.fallback_provider.generate(prompt)
            raise

4. Decorator Pattern

Used to extend functionality:

def rate_limit(func):
    async def wrapper(*args, **kwargs):
        # Rate limiting logic
        return await func(*args, **kwargs)
    return wrapper

@router.post("/recommendations")
@rate_limit
async def get_recommendations():
    # Endpoint logic

5. Adapter Pattern

Used to integrate with external services:

class WeatherServiceAdapter:
    def __init__(self, api_key: str):
        self.api_key = api_key

    async def get_weather(self, location: str) -> Dict[str, Any]:
        # Convert external API format to our internal format

Project Structure

travelsage-backend/
├── app/                    # Application code
│   ├── api/                # API routes and endpoints
│   │   ├── deps.py         # Authentication dependencies
│   │   ├── deps_services.py # Service dependencies
│   │   └── v1/             # API version 1 endpoints
│   ├── core/               # Core application components
│   │   ├── config.py       # Configuration settings
│   │   ├── db.py           # Database setup
│   │   └── security.py     # Security utilities
│   ├── models/             # Database models and schemas
│   ├── services/           # Business logic services
│   │   ├── ai/             # AI service components
│   │   ├── cache/          # Caching services
│   │   └── travel_services.py # Travel recommendation services
│   └── main.py             # Application entry point
├── tests/                  # Test suite
│   ├── conftest.py         # Test configuration
│   ├── performance/        # Performance and load tests
│   └── services/           # Service tests
├── docs/                   # Documentation
├── alembic/                # Database migrations
└── requirements.txt        # Python dependencies

Data Flow

A typical request for travel recommendations follows this flow:

  1. API Layer: FastAPI route receives the request and validates input
  2. Service Layer: TravelService processes the request
  3. Cache Check: PostgreSQL cache is checked for existing recommendations
  4. AI Service: If no cache hit, AI service generates recommendations
  5. Storage: Initial recommendations are stored for potential enrichment
  6. Enrichment: For authenticated users, recommendations are enriched with detailed data
  7. Cache Update: Results are cached for future requests
  8. Response: Formatted recommendations are returned to the client

Testing Strategy

MacumbaTravel uses a comprehensive testing approach:

  1. Unit Tests: Test individual components in isolation
  2. Integration Tests: Test interactions between components
  3. API Tests: Test API endpoints end-to-end
  4. Performance Tests: Test system performance under load
  5. Mocking: External dependencies are mocked for deterministic testing

Why These Choices?

1. FastAPI

  • High performance (based on Starlette and Pydantic)
  • Native support for async/await
  • Automatic OpenAPI documentation
  • Type checking and validation
  • Dependency injection system

2. PostgreSQL

  • ACID compliance for data integrity
  • Rich feature set (JSON support, full-text search)
  • Scalability options
  • Strong community and tooling

3. PostgreSQL Cache

  • Cost-effective caching solution (saves ~$110/month vs Redis)
  • Leverages existing database infrastructure
  • JSONB support for complex data structures
  • Built-in TTL support with expiration timestamps
  • Fault-tolerant design that doesn't break application if cache is unavailable

4. Asynchronous Architecture

  • Better resource utilization
  • Improved responsiveness under load
  • Ability to handle concurrent requests efficiently
  • Well-suited for I/O-bound operations (API calls, database queries)

Environment Management and Production Safety

Database Schema Management

The application implements environment-aware database schema management:

  • Development Environment: Automatic schema creation and updates enabled
  • Production Environment: Schema auto-creation disabled for safety
  • Migration-Based Approach: Production uses Alembic migrations for controlled schema changes
  • Environment Detection: Automatic detection based on ENVIRONMENT configuration

Production Safety Features:

  • Schema auto-creation is explicitly disabled in production
  • Database changes require explicit migration scripts
  • Prevents accidental schema modifications in live environments
  • Supports rollback capabilities through migration versioning

Monitoring and Observability

Health Checks

  • Liveness Probe (/live): Basic application health
  • Readiness Probe (/ready): Service dependency health
  • Health Check (/health): Comprehensive system status

Metrics and Monitoring

  • Prometheus Integration: Standard metrics format at /metrics endpoint
  • Request Metrics: HTTP request counts, durations, and error rates
  • Service Metrics: AI service usage, database connections, cache performance
  • Custom Business Metrics: Travel recommendation success rates, user engagement

Resilience and Reliability Patterns

Circuit Breaker Pattern

The application implements circuit breakers to handle external service failures gracefully:

# Circuit breaker for external services
self.flights_circuit_breaker = CircuitBreaker(
    failure_threshold=3,      # Open after 3 consecutive failures
    recovery_timeout=30,      # Try again after 30 seconds
    name="flights_service"
)

# Usage in service calls
try:
    result = await self.flights_circuit_breaker.call(
        self.flights_service.get_detailed_flights,
        origin=departure_city,
        destination=destination,
        adults=travelers,
    )
except CircuitBreakerOpenError:
    # Fallback to cached or default data
    result = self._get_fallback_data()

Circuit Breaker Configuration:

  • Flights Service: 3 failures, 30s recovery (critical for pricing)
  • Google Maps: 3 failures, 30s recovery (important for routing)
  • Image Service: 5 failures, 60s recovery (non-critical, longer timeout)
  • Accommodation Service: 3 failures, 30s recovery (important for cost estimates)

Benefits:

  • Prevent Cascading Failures: Stops trying failing services immediately
  • Faster Response Times: Fails fast instead of waiting for timeouts
  • Graceful Degradation: Uses fallback data when services are unavailable
  • Automatic Recovery: Tests service health and recovers automatically

Progressive Enhancement and Transparency

The recommendation system provides transparency through:

  1. Provenance Tracking: Each data component shows its source (AI, Google Maps, Amadeus, etc.)
  2. Data Freshness: Timestamps showing when each piece of data was fetched
  3. Cost Breakdown: Detailed breakdown of all cost components
  4. Component Status: Success/failure status for each enrichment step
  5. Status Endpoints: Real-time enrichment progress for progressive loading

Fallback Strategies

Each external service has defined fallback strategies: - Transportation: Fall back to AI-provided transport options - Flights: Use estimated pricing based on distance - Accommodations: Provide budget estimates without specific properties - Images: Use default destination images or generic travel images

Security Enhancements

Recent security improvements include: - Token Hashing: Email verification and password reset tokens are securely hashed - Secure Comparison: Constant-time token verification to prevent timing attacks - Proper Token Lifecycle: Tokens are invalidated after use

Scalability Considerations

The architecture is designed with scalability in mind:

  1. Horizontal Scaling: Stateless API servers can be scaled horizontally
  2. Caching Strategy: Reduces load on expensive operations with intelligent TTL
  3. Database Optimization: Indexes and query optimization with JSONB support
  4. Asynchronous Processing: Efficient handling of concurrent requests with timeout management
  5. Rate Limiting: Protection against abuse and overload
  6. Monitoring Integration: Prometheus metrics for auto-scaling decisions
  7. Circuit Breakers: Prevent resource exhaustion from failing external services
  8. Progressive Loading: Reduces perceived latency through status endpoints

Conclusion

MacumbaTravel's architecture combines modern development paradigms with practical design patterns to create a maintainable, testable, and scalable system. The choices made in the architecture reflect the specific needs of a travel recommendation system, with particular attention to performance, reliability, and developer experience.

As you work with the codebase, understanding these architectural principles will help you make consistent decisions that align with the overall design philosophy of the application.

Further Reading