# Migration Guide: Microservices to Modular Monolith This guide explains the transformation from the microservices architecture to the modular monolithic architecture. ## Overview of Changes ### Architecture Transformation **Before (Microservices)**: - Multiple independent services (8+ services) - HTTP-based inter-service communication - Redis pub/sub for events - API Gateway for routing - Service discovery and health checking - Separate Docker containers per service **After (Modular Monolith)**: - Single application with modular structure - Direct function calls via dependency injection - In-process event bus - Integrated routing in main application - Single Docker container - Separate databases per module (preserved isolation) ## Key Architectural Differences ### 1. Service Communication #### Microservices Approach ```python # HTTP call to another service async with aiohttp.ClientSession() as session: url = f"{SENSOR_SERVICE_URL}/sensors/{sensor_id}" async with session.get(url) as response: data = await response.json() ``` #### Modular Monolith Approach ```python # Direct function call with dependency injection from modules.sensors import SensorService from core.dependencies import get_sensors_db sensor_service = SensorService(db=await get_sensors_db(), redis=None) data = await sensor_service.get_sensor_details(sensor_id) ``` ### 2. Event Communication #### Microservices Approach (Redis Pub/Sub) ```python # Publishing await redis.publish("energy_data", json.dumps(data)) # Subscribing pubsub = redis.pubsub() await pubsub.subscribe("energy_data") message = await pubsub.get_message() ``` #### Modular Monolith Approach (Event Bus) ```python # Publishing from core.events import event_bus, EventTopics await event_bus.publish(EventTopics.ENERGY_DATA, data) # Subscribing def handle_energy_data(data): # Process data pass event_bus.subscribe(EventTopics.ENERGY_DATA, handle_energy_data) ``` ### 3. Database Access #### Microservices Approach ```python # Each service has its own database connection from database import get_database db = await get_database() # Returns service-specific database ``` #### Modular Monolith Approach ```python # Centralized database manager with module-specific databases from core.database import db_manager sensors_db = db_manager.sensors_db demand_response_db = db_manager.demand_response_db ``` ### 4. Application Structure #### Microservices Structure ``` microservices/ ├── api-gateway/ │ └── main.py (port 8000) ├── sensor-service/ │ └── main.py (port 8007) ├── demand-response-service/ │ └── main.py (port 8003) ├── data-ingestion-service/ │ └── main.py (port 8008) └── docker-compose.yml (8+ containers) ``` #### Modular Monolith Structure ``` monolith/ ├── src/ │ ├── main.py (single entry point) │ ├── core/ (shared infrastructure) │ └── modules/ │ ├── sensors/ │ ├── demand_response/ │ └── data_ingestion/ └── docker-compose.yml (1 container) ``` ## Migration Steps ### Phase 1: Preparation 1. **Backup existing data**: ```bash # Backup all MongoDB databases mongodump --uri="mongodb://admin:password123@localhost:27017" --out=/backup/microservices ``` 2. **Document current API endpoints**: - List all endpoints from each microservice - Document inter-service communication patterns - Identify Redis pub/sub channels in use 3. **Review environment variables**: - Consolidate environment variables - Update connection strings for external MongoDB and Redis ### Phase 2: Deploy Modular Monolith 1. **Configure environment**: ```bash cd /path/to/monolith cp .env.example .env # Edit .env with MongoDB and Redis connection strings ``` 2. **Build and deploy**: ```bash docker-compose up --build -d ``` 3. **Verify health**: ```bash curl http://localhost:8000/health curl http://localhost:8000/api/v1/overview ``` ### Phase 3: Data Migration (if needed) The modular monolith uses the **same database structure** as the microservices, so typically no data migration is needed. However, verify: 1. **Database names match**: - `energy_dashboard_sensors` - `energy_dashboard_demand_response` - `digitalmente_ingestion` 2. **Collections are accessible**: ```bash # Connect to MongoDB mongosh mongodb://admin:password123@mongodb-host:27017/?authSource=admin # Check databases show dbs # Verify collections in each database use energy_dashboard_sensors show collections ``` ### Phase 4: API Client Migration Update API clients to point to the new monolith endpoint: **Before**: - Sensor API: `http://api-gateway:8000/api/v1/sensors/*` - DR API: `http://api-gateway:8000/api/v1/demand-response/*` **After**: - All APIs: `http://monolith:8000/api/v1/*` The API paths remain the same, only the host changes! ### Phase 5: Decommission Microservices Once the monolith is stable: 1. **Stop microservices**: ```bash cd /path/to/microservices docker-compose down ``` 2. **Keep backups** for at least 30 days 3. **Archive microservices code** for reference ## Benefits of the Migration ### Operational Simplification | Aspect | Microservices | Modular Monolith | Improvement | |--------|---------------|------------------|-------------| | **Containers** | 8+ containers | 1 container | 87% reduction | | **Network calls** | HTTP between services | In-process calls | ~100x faster | | **Deployment complexity** | Coordinate 8+ services | Single deployment | Much simpler | | **Monitoring** | 8+ health endpoints | 1 health endpoint | Easier | | **Log aggregation** | 8+ log sources | 1 log source | Simpler | ### Performance Improvements 1. **Reduced latency**: - Inter-service HTTP calls: ~10-50ms - Direct function calls: ~0.01-0.1ms - **Improvement**: 100-1000x faster 2. **Reduced network overhead**: - No HTTP serialization/deserialization - No network round-trips - No service discovery delays 3. **Shared resources**: - Single database connection pool - Shared Redis connection (if enabled) - Shared in-memory caches ### Development Benefits 1. **Easier debugging**: - Single process to debug - Direct stack traces across modules - No distributed tracing needed 2. **Simpler testing**: - Test entire flow in one process - No need to mock HTTP calls - Integration tests run faster 3. **Faster development**: - Single application to run locally - Immediate code changes (with reload) - No service orchestration needed ## Preserved Benefits from Microservices ### Module Isolation Each module maintains clear boundaries: - Separate directory structure - Own models and business logic - Dedicated database (data isolation) - Clear public interfaces ### Independent Scaling (Future) If needed, modules can be extracted back into microservices: - Clean module boundaries make extraction easy - Database per module already separated - Event bus can switch to Redis pub/sub - Direct calls can switch to HTTP calls ### Team Organization Teams can still own modules: - Sensors team owns `modules/sensors/` - DR team owns `modules/demand_response/` - Clear ownership and responsibilities ## Rollback Strategy If you need to rollback to microservices: 1. **Keep microservices code** in the repository 2. **Database unchanged**: Both architectures use the same databases 3. **Redeploy microservices**: ```bash cd /path/to/microservices docker-compose up -d ``` 4. **Update API clients** to point back to API Gateway ## Monitoring and Observability ### Health Checks **Single health endpoint**: ```bash curl http://localhost:8000/health ``` Returns: ```json { "service": "Energy Dashboard Monolith", "status": "healthy", "components": { "database": "healthy", "redis": "healthy", "event_bus": "healthy" }, "modules": { "sensors": "loaded", "demand_response": "loaded", "data_ingestion": "loaded" } } ``` ### Logging All logs in one place: ```bash # Docker logs docker-compose logs -f monolith # Application logs docker-compose logs -f monolith | grep "ERROR" ``` ### Metrics System overview endpoint: ```bash curl http://localhost:8000/api/v1/overview ``` ## Common Migration Issues ### Issue: Module Import Errors **Problem**: `ModuleNotFoundError: No module named 'src.modules'` **Solution**: ```bash # Set PYTHONPATH export PYTHONPATH=/app # Or in docker-compose.yml environment: - PYTHONPATH=/app ``` ### Issue: Database Connection Errors **Problem**: Cannot connect to MongoDB **Solution**: 1. Verify MongoDB is accessible: ```bash docker-compose exec monolith ping mongodb-host ``` 2. Check connection string in `.env` 3. Ensure network connectivity ### Issue: Redis Connection Errors **Problem**: Redis connection failed but app should work **Solution**: Redis is optional. Set in `.env`: ``` REDIS_ENABLED=false ``` ### Issue: Event Subscribers Not Receiving Events **Problem**: Events published but subscribers not called **Solution**: Ensure subscribers are registered before events are published: ```python # Register subscriber in lifespan startup @asynccontextmanager async def lifespan(app: FastAPI): # Subscribe before publishing event_bus.subscribe(EventTopics.ENERGY_DATA, handle_energy) yield ``` ## Testing the Migration ### 1. Functional Testing Test each module's endpoints: ```bash # Sensors curl http://localhost:8000/api/v1/sensors/get curl http://localhost:8000/api/v1/rooms # Analytics curl http://localhost:8000/api/v1/analytics/summary # Health curl http://localhost:8000/health ``` ### 2. Load Testing Compare performance: ```bash # Microservices ab -n 1000 -c 10 http://localhost:8000/api/v1/sensors/get # Modular Monolith ab -n 1000 -c 10 http://localhost:8000/api/v1/sensors/get ``` Expected: Modular monolith should be significantly faster. ### 3. WebSocket Testing Test real-time features: ```javascript const ws = new WebSocket('ws://localhost:8000/api/v1/ws'); ws.onmessage = (event) => console.log('Received:', event.data); ``` ## FAQ ### Q: Do I need to migrate the database? **A**: No, the modular monolith uses the same database structure as the microservices. ### Q: Can I scale individual modules? **A**: Not independently. The entire monolith scales together. If you need independent scaling, consider keeping the microservices architecture or using horizontal scaling with load balancers. ### Q: What happens to Redis pub/sub? **A**: Replaced with an in-process event bus. Redis can still be used for caching if `REDIS_ENABLED=true`. ### Q: Are the API endpoints the same? **A**: Yes, the API paths remain identical. Only the host changes. ### Q: Can I extract modules back to microservices later? **A**: Yes, the modular structure makes it easy to extract modules back into separate services if needed. ### Q: How do I add a new module? **A**: See the "Adding a New Module" section in README.md. ### Q: Is this suitable for production? **A**: Yes, modular monoliths are production-ready and often more reliable than microservices for small-to-medium scale applications. ## Next Steps 1. **Deploy to staging** and run full test suite 2. **Monitor performance** and compare with microservices 3. **Gradual rollout** to production (canary or blue-green deployment) 4. **Decommission microservices** after 30 days of stable operation 5. **Update documentation** and team training ## Support For issues or questions about the migration: 1. Check this guide and README.md 2. Review application logs: `docker-compose logs monolith` 3. Test health endpoint: `curl http://localhost:8000/health` 4. Contact the development team