Completed

✅ Multi-Database Architecture Pattern (2025-12-23)

  • Refactored leave module to self-contained structure
  • Organized repositories under repository/mongodb/ subdirectory
  • Clear separation between interface (ports) and implementation (mongodb)
  • Makes it easy to add alternative database implementations in the future
Structure:
modules/leave/
├── domain/                 # Domain models
├── ports/                  # Interfaces (database-agnostic)
├── repository/
│   └── mongodb/            # MongoDB implementations
│       ├── leave_balance_repository.go
│       ├── leave_balance_query_repository.go
│       ├── leave_request_repository.go
│       ├── leave_query_repository.go
│       └── leave_type_repository.go
├── services/               # Business logic
└── module.go               # Module wiring

Planned

🔜 Composite Repository Pattern for Caching

Priority: High
Estimated Effort: Medium (2-3 days)
Dependencies: Redis client integration

Overview

Implement a caching layer using the Composite/Decorator pattern to significantly improve read performance for frequently accessed data.

Implementation Plan

Phase 1: Infrastructure Setup
  1. Add Redis client to app.Container
  2. Configure Redis connection in config/config.go
  3. Add Redis health check to server startup
Phase 2: Redis Cache Repository Create cache implementations in repository/redis/:
repository/
├── mongodb/              # Existing
└── redis/                # New
    ├── leave_balance_cache.go
    ├── leave_type_cache.go
    └── cache_helpers.go
Phase 3: Composite Repository Create composite wrappers in repository/composite/:
repository/
├── mongodb/
├── redis/
└── composite/           # New
    ├── cached_leave_balance_repository.go
    └── cached_leave_type_repository.go
Phase 4: Module Integration Update module.go to optionally wrap repositories with caching:
// Development: Direct MongoDB
if config.Cache.Enabled {
    balanceRepo = composite.NewCachedLeaveBalanceRepository(
        leaveMongoRepo.NewLeaveBalanceRepository(...),
        redis.NewLeaveBalanceCache(...),
        logger,
    )
} else {
    balanceRepo = leaveMongoRepo.NewLeaveBalanceRepository(...)
}

Target Metrics

  • Read Latency: 50ms → 1-3ms (17-50x faster)
  • Database Load: 90% reduction for cached data
  • Cache Hit Rate: Target 80%+ for leave balances and types

Caching Strategy

What to Cache:
  1. Leave Types (High priority)
    • Rarely changes
    • Read on every request
    • TTL: 1 hour
  2. Leave Balances (Medium priority)
    • Changes infrequently
    • Read frequently
    • TTL: 5 minutes
    • Invalidate on: Deduct, Create, Update
  3. Leave Requests (Low priority)
    • Changes frequently
    • CQRS read model is already optimized
    • May consider later if needed
Cache Invalidation:
  • Write operations automatically invalidate related cache keys
  • Use cache tags for bulk invalidation
  • Fallback to database on cache errors

Configuration

# config.yaml
cache:
  enabled: true
  redis:
    host: localhost
    port: 6379
    password: ""
    db: 0
  ttl:
    leave_types: 3600 # 1 hour
    leave_balances: 300 # 5 minutes

Testing Strategy

  1. Unit tests for cache repository implementations
  2. Integration tests for composite pattern
  3. Load tests to verify performance improvements
  4. Chaos tests for cache failure scenarios

Rollout Plan

  1. Deploy to staging with caching enabled
  2. Monitor cache hit/miss rates
  3. Tune TTL values based on metrics
  4. Gradual rollout to production (feature flag)

🔮 Future Considerations

PostgreSQL Support (Optional)

Priority: Low
Effort: Medium
If needed in the future, add PostgreSQL implementation:
repository/
├── mongodb/
├── postgres/            # New
│   ├── leave_balance_repository.go
│   └── ...
├── redis/
└── composite/
Update module.go to switch based on config:
switch config.Database.Type {
case "mongodb":
    repo = leaveMongoRepo.NewLeaveBalanceRepository(...)
case "postgres":
    repo = postgresRepo.NewLeaveBalanceRepository(...)
}

Read Replicas

Priority: Low
Effort: High
For very high read loads:
  • Separate read/write database connections
  • Route queries to read replicas
  • Route commands to primary database

Notes

Why Composite Pattern for Caching?

  • ✅ Transparent to service layer (no code changes)
  • ✅ Easy to enable/disable via configuration
  • ✅ Single Responsibility: Each repository does one thing
  • ✅ Testable: Can test cache and database separately
  • ✅ Flexible: Easy to swap cache implementations

Alternative Approaches Considered

  1. Cache in Service Layer: ❌ Violates SRP, harder to test
  2. Cache in HTTP Handler: ❌ Doesn’t help internal service calls
  3. Decorator Pattern: ✅ Same as Composite (synonym in this context)

Metrics to Track

Performance

  • Average response time for leave balance queries
  • Database query count per minute
  • Cache hit/miss ratio
  • Redis memory usage

Business

  • API availability (uptime)
  • Error rate
  • User satisfaction (response time < 100ms)

References