modular monolythic
This commit is contained in:
22
monolith/.env.example
Normal file
22
monolith/.env.example
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# MongoDB Configuration (external deployment)
|
||||||
|
# Update with your MongoDB connection string
|
||||||
|
MONGO_URL=mongodb://admin:password123@mongodb-host:27017/?authSource=admin
|
||||||
|
|
||||||
|
# Redis Configuration (external deployment, optional)
|
||||||
|
# Update with your Redis connection string
|
||||||
|
REDIS_URL=redis://redis-host:6379
|
||||||
|
REDIS_ENABLED=false
|
||||||
|
|
||||||
|
# FTP Configuration
|
||||||
|
FTP_SA4CPS_HOST=ftp.sa4cps.pt
|
||||||
|
FTP_SA4CPS_PORT=21
|
||||||
|
FTP_SA4CPS_USERNAME=curvascarga@sa4cps.pt
|
||||||
|
FTP_SA4CPS_PASSWORD=
|
||||||
|
FTP_SA4CPS_REMOTE_PATH=/SLGs/
|
||||||
|
FTP_CHECK_INTERVAL=21600
|
||||||
|
FTP_SKIP_INITIAL_SCAN=true
|
||||||
|
|
||||||
|
# Application Settings
|
||||||
|
DEBUG=false
|
||||||
|
HOST=0.0.0.0
|
||||||
|
PORT=8000
|
||||||
28
monolith/Dockerfile
Normal file
28
monolith/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy requirements and install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY src/ ./src/
|
||||||
|
|
||||||
|
# Set Python path
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||||
|
CMD python -c "import requests; requests.get('http://localhost:8000/health')"
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
480
monolith/MIGRATION.md
Normal file
480
monolith/MIGRATION.md
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
# 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
|
||||||
453
monolith/README.md
Normal file
453
monolith/README.md
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
# Energy Dashboard - Modular Monolith
|
||||||
|
|
||||||
|
This is the modular monolithic architecture version of the Energy Dashboard, refactored from the original microservices architecture.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
The application is structured as a **modular monolith**, combining the benefits of:
|
||||||
|
- **Monolithic deployment**: Single application, simpler operations
|
||||||
|
- **Modular design**: Clear module boundaries, maintainability
|
||||||
|
|
||||||
|
### Key Architectural Decisions
|
||||||
|
|
||||||
|
1. **Single Application**: All modules run in one process
|
||||||
|
2. **Module Isolation**: Each module has its own directory and clear interfaces
|
||||||
|
3. **Separate Databases**: Each module maintains its own database for data isolation
|
||||||
|
4. **In-Process Event Bus**: Replaces Redis pub/sub for inter-module communication
|
||||||
|
5. **Direct Dependency Injection**: Modules communicate directly via function calls
|
||||||
|
6. **Shared Core**: Common infrastructure (database, events, config) shared across modules
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
monolith/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.py # Main FastAPI application
|
||||||
|
│ ├── core/ # Shared core infrastructure
|
||||||
|
│ │ ├── config.py # Centralized configuration
|
||||||
|
│ │ ├── database.py # Database connection manager
|
||||||
|
│ │ ├── events.py # In-process event bus
|
||||||
|
│ │ ├── redis.py # Optional Redis cache
|
||||||
|
│ │ ├── dependencies.py # FastAPI dependencies
|
||||||
|
│ │ └── logging_config.py # Logging setup
|
||||||
|
│ ├── modules/ # Business modules
|
||||||
|
│ │ ├── sensors/ # Sensor management module
|
||||||
|
│ │ │ ├── __init__.py
|
||||||
|
│ │ │ ├── router.py # API routes
|
||||||
|
│ │ │ ├── models.py # Data models
|
||||||
|
│ │ │ ├── sensor_service.py # Business logic
|
||||||
|
│ │ │ ├── room_service.py
|
||||||
|
│ │ │ ├── analytics_service.py
|
||||||
|
│ │ │ └── websocket_manager.py
|
||||||
|
│ │ ├── demand_response/ # Demand response module
|
||||||
|
│ │ │ ├── __init__.py
|
||||||
|
│ │ │ ├── models.py
|
||||||
|
│ │ │ └── demand_response_service.py
|
||||||
|
│ │ └── data_ingestion/ # Data ingestion module
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── config.py
|
||||||
|
│ │ ├── ftp_monitor.py
|
||||||
|
│ │ ├── slg_processor.py
|
||||||
|
│ │ └── database.py
|
||||||
|
│ └── api/ # API layer (if needed)
|
||||||
|
├── config/ # Configuration files
|
||||||
|
├── tests/ # Test files
|
||||||
|
├── requirements.txt # Python dependencies
|
||||||
|
├── Dockerfile # Docker build file
|
||||||
|
├── docker-compose.yml # Docker Compose configuration
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
### 1. Sensors Module (`src/modules/sensors`)
|
||||||
|
|
||||||
|
**Responsibility**: Sensor management, room management, real-time data, and analytics
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Sensor CRUD operations
|
||||||
|
- Room management
|
||||||
|
- Real-time data ingestion
|
||||||
|
- Analytics and reporting
|
||||||
|
- WebSocket support for live data streaming
|
||||||
|
|
||||||
|
**Database**: `energy_dashboard_sensors`
|
||||||
|
|
||||||
|
**API Endpoints**: `/api/v1/sensors/*`, `/api/v1/rooms/*`, `/api/v1/analytics/*`
|
||||||
|
|
||||||
|
### 2. Demand Response Module (`src/modules/demand_response`)
|
||||||
|
|
||||||
|
**Responsibility**: Grid interaction, demand response events, and load management
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Demand response event management
|
||||||
|
- Device flexibility calculation
|
||||||
|
- Auto-response configuration
|
||||||
|
- Load reduction requests
|
||||||
|
|
||||||
|
**Database**: `energy_dashboard_demand_response`
|
||||||
|
|
||||||
|
**API Endpoints**: `/api/v1/demand-response/*`
|
||||||
|
|
||||||
|
### 3. Data Ingestion Module (`src/modules/data_ingestion`)
|
||||||
|
|
||||||
|
**Responsibility**: FTP monitoring and SA4CPS data processing
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- FTP file monitoring
|
||||||
|
- .sgl_v2 file processing
|
||||||
|
- Dynamic collection management
|
||||||
|
- Duplicate detection
|
||||||
|
|
||||||
|
**Database**: `digitalmente_ingestion`
|
||||||
|
|
||||||
|
**API Endpoints**: `/api/v1/ingestion/*`
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
### Event Bus (`src/core/events.py`)
|
||||||
|
|
||||||
|
Replaces Redis pub/sub with an in-process event bus for inter-module communication.
|
||||||
|
|
||||||
|
**Standard Event Topics**:
|
||||||
|
- `energy_data`: Energy consumption updates
|
||||||
|
- `dr_events`: Demand response events
|
||||||
|
- `sensor_events`: Sensor-related events
|
||||||
|
- `system_events`: System-level events
|
||||||
|
- `data_ingestion`: Data ingestion events
|
||||||
|
|
||||||
|
**Usage Example**:
|
||||||
|
```python
|
||||||
|
from core.events import event_bus, EventTopics
|
||||||
|
|
||||||
|
# Publish event
|
||||||
|
await event_bus.publish(EventTopics.ENERGY_DATA, {"sensor_id": "sensor_1", "value": 3.5})
|
||||||
|
|
||||||
|
# Subscribe to events
|
||||||
|
def handle_energy_data(data):
|
||||||
|
print(f"Received energy data: {data}")
|
||||||
|
|
||||||
|
event_bus.subscribe(EventTopics.ENERGY_DATA, handle_energy_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Manager (`src/core/database.py`)
|
||||||
|
|
||||||
|
Centralized database connection management with separate databases per module.
|
||||||
|
|
||||||
|
**Available Databases**:
|
||||||
|
- `main_db`: Main application database
|
||||||
|
- `sensors_db`: Sensors module database
|
||||||
|
- `demand_response_db`: Demand response module database
|
||||||
|
- `data_ingestion_db`: Data ingestion module database
|
||||||
|
|
||||||
|
**Usage Example**:
|
||||||
|
```python
|
||||||
|
from core.dependencies import get_sensors_db
|
||||||
|
from fastapi import Depends
|
||||||
|
|
||||||
|
async def my_endpoint(db=Depends(get_sensors_db)):
|
||||||
|
result = await db.sensors.find_one({"sensor_id": "sensor_1"})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration (`src/core/config.py`)
|
||||||
|
|
||||||
|
Centralized configuration using Pydantic Settings.
|
||||||
|
|
||||||
|
**Configuration Sources**:
|
||||||
|
1. Environment variables
|
||||||
|
2. `.env` file (if present)
|
||||||
|
3. Default values
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python 3.11+
|
||||||
|
- MongoDB 7.0+ (deployed separately)
|
||||||
|
- Redis 7+ (optional, for caching - deployed separately)
|
||||||
|
- Docker and Docker Compose (for containerized deployment)
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
1. **Install dependencies**:
|
||||||
|
```bash
|
||||||
|
cd monolith
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure environment**:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your MongoDB and Redis connection strings
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ensure MongoDB and Redis are accessible**:
|
||||||
|
- MongoDB should be running and accessible at the URL specified in `MONGO_URL`
|
||||||
|
- Redis (optional) should be accessible at the URL specified in `REDIS_URL`
|
||||||
|
|
||||||
|
4. **Run the application**:
|
||||||
|
```bash
|
||||||
|
cd src
|
||||||
|
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Access the application**:
|
||||||
|
- API: http://localhost:8000
|
||||||
|
- Health Check: http://localhost:8000/health
|
||||||
|
- API Docs: http://localhost:8000/docs
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
**Note**: MongoDB and Redis are deployed separately and must be accessible before starting the application.
|
||||||
|
|
||||||
|
1. **Configure environment variables**:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your MongoDB and Redis connection strings
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Build and start the application**:
|
||||||
|
```bash
|
||||||
|
cd monolith
|
||||||
|
docker-compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **View logs**:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f monolith
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Stop the application**:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Global Endpoints
|
||||||
|
|
||||||
|
- `GET /`: Root endpoint
|
||||||
|
- `GET /health`: Global health check
|
||||||
|
- `GET /api/v1/overview`: System overview
|
||||||
|
|
||||||
|
### Sensors Module
|
||||||
|
|
||||||
|
- `GET /api/v1/sensors/get`: Get sensors with filters
|
||||||
|
- `GET /api/v1/sensors/{sensor_id}`: Get sensor details
|
||||||
|
- `GET /api/v1/sensors/{sensor_id}/data`: Get sensor data
|
||||||
|
- `POST /api/v1/sensors`: Create sensor
|
||||||
|
- `PUT /api/v1/sensors/{sensor_id}`: Update sensor
|
||||||
|
- `DELETE /api/v1/sensors/{sensor_id}`: Delete sensor
|
||||||
|
|
||||||
|
- `GET /api/v1/rooms`: Get all rooms
|
||||||
|
- `GET /api/v1/rooms/names`: Get room names
|
||||||
|
- `POST /api/v1/rooms`: Create room
|
||||||
|
- `GET /api/v1/rooms/{room_name}`: Get room details
|
||||||
|
- `PUT /api/v1/rooms/{room_name}`: Update room
|
||||||
|
- `DELETE /api/v1/rooms/{room_name}`: Delete room
|
||||||
|
|
||||||
|
- `GET /api/v1/analytics/summary`: Analytics summary
|
||||||
|
- `GET /api/v1/analytics/energy`: Energy analytics
|
||||||
|
- `POST /api/v1/data/query`: Advanced data query
|
||||||
|
|
||||||
|
- `WS /api/v1/ws`: WebSocket for real-time data
|
||||||
|
|
||||||
|
### Demand Response Module
|
||||||
|
|
||||||
|
- Endpoints for demand response events, invitations, and device management
|
||||||
|
- (To be fully documented when router is added)
|
||||||
|
|
||||||
|
### Data Ingestion Module
|
||||||
|
|
||||||
|
- Endpoints for FTP monitoring status and manual triggers
|
||||||
|
- (To be fully documented when router is added)
|
||||||
|
|
||||||
|
## Inter-Module Communication
|
||||||
|
|
||||||
|
Modules communicate in two ways:
|
||||||
|
|
||||||
|
### 1. Direct Dependency Injection
|
||||||
|
|
||||||
|
For synchronous operations, modules directly import and call each other's services:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from modules.sensors import SensorService
|
||||||
|
from core.dependencies import get_sensors_db
|
||||||
|
|
||||||
|
sensor_service = SensorService(db=await get_sensors_db(), redis=None)
|
||||||
|
sensors = await sensor_service.get_sensors()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event-Driven Communication
|
||||||
|
|
||||||
|
For asynchronous operations, modules use the event bus:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from core.events import event_bus, EventTopics
|
||||||
|
|
||||||
|
# Publisher
|
||||||
|
await event_bus.publish(EventTopics.ENERGY_DATA, {
|
||||||
|
"sensor_id": "sensor_1",
|
||||||
|
"value": 3.5,
|
||||||
|
"timestamp": 1234567890
|
||||||
|
})
|
||||||
|
|
||||||
|
# Subscriber
|
||||||
|
async def handle_energy_update(data):
|
||||||
|
print(f"Energy update: {data}")
|
||||||
|
|
||||||
|
event_bus.subscribe(EventTopics.ENERGY_DATA, handle_energy_update)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Background Tasks
|
||||||
|
|
||||||
|
The application runs several background tasks:
|
||||||
|
|
||||||
|
1. **Room Metrics Aggregation** (every 5 minutes)
|
||||||
|
- Aggregates sensor data into room-level metrics
|
||||||
|
|
||||||
|
2. **Data Cleanup** (daily)
|
||||||
|
- Removes sensor data older than 90 days
|
||||||
|
|
||||||
|
3. **Event Scheduler** (every 60 seconds)
|
||||||
|
- Checks and executes scheduled demand response events
|
||||||
|
|
||||||
|
4. **Auto Response** (every 30 seconds)
|
||||||
|
- Processes automatic demand response opportunities
|
||||||
|
|
||||||
|
5. **FTP Monitoring** (every 6 hours, configurable)
|
||||||
|
- Monitors FTP server for new SA4CPS data files
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
Key environment variables:
|
||||||
|
|
||||||
|
### Database
|
||||||
|
- `MONGO_URL`: MongoDB connection string
|
||||||
|
- `REDIS_URL`: Redis connection string
|
||||||
|
- `REDIS_ENABLED`: Enable/disable Redis (true/false)
|
||||||
|
|
||||||
|
### Application
|
||||||
|
- `DEBUG`: Enable debug mode (true/false)
|
||||||
|
- `HOST`: Application host (default: 0.0.0.0)
|
||||||
|
- `PORT`: Application port (default: 8000)
|
||||||
|
|
||||||
|
### FTP
|
||||||
|
- `FTP_SA4CPS_HOST`: FTP server host
|
||||||
|
- `FTP_SA4CPS_PORT`: FTP server port
|
||||||
|
- `FTP_SA4CPS_USERNAME`: FTP username
|
||||||
|
- `FTP_SA4CPS_PASSWORD`: FTP password
|
||||||
|
- `FTP_SA4CPS_REMOTE_PATH`: Remote directory path
|
||||||
|
- `FTP_CHECK_INTERVAL`: Check interval in seconds
|
||||||
|
- `FTP_SKIP_INITIAL_SCAN`: Skip initial FTP scan (true/false)
|
||||||
|
|
||||||
|
## Migration from Microservices
|
||||||
|
|
||||||
|
See [MIGRATION.md](MIGRATION.md) for detailed migration guide.
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### Adding a New Module
|
||||||
|
|
||||||
|
1. Create module directory: `src/modules/new_module/`
|
||||||
|
2. Add module files:
|
||||||
|
- `__init__.py`: Module exports
|
||||||
|
- `models.py`: Pydantic models
|
||||||
|
- `service.py`: Business logic
|
||||||
|
- `router.py`: API routes
|
||||||
|
|
||||||
|
3. Register module in main application:
|
||||||
|
```python
|
||||||
|
from modules.new_module.router import router as new_module_router
|
||||||
|
app.include_router(new_module_router, prefix="/api/v1/new-module", tags=["new-module"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding an Event Topic
|
||||||
|
|
||||||
|
1. Add topic to `EventTopics` class in `src/core/events.py`:
|
||||||
|
```python
|
||||||
|
class EventTopics:
|
||||||
|
NEW_TOPIC = "new_topic"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Use in your module:
|
||||||
|
```python
|
||||||
|
from core.events import event_bus, EventTopics
|
||||||
|
await event_bus.publish(EventTopics.NEW_TOPIC, data)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
pytest --cov=src --cov-report=html
|
||||||
|
|
||||||
|
# Run specific module tests
|
||||||
|
pytest tests/modules/sensors/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring and Logging
|
||||||
|
|
||||||
|
- **Logs**: Application logs to stdout
|
||||||
|
- **Log Level**: Controlled by `DEBUG` environment variable
|
||||||
|
- **Health Checks**: Available at `/health` endpoint
|
||||||
|
- **Metrics**: System overview at `/api/v1/overview`
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- **Database Indexing**: Ensure proper indexes on frequently queried fields
|
||||||
|
- **Redis Caching**: Enable Redis for improved performance (optional)
|
||||||
|
- **Connection Pooling**: Motor (MongoDB) and Redis clients handle connection pooling
|
||||||
|
- **Async Operations**: All I/O operations are asynchronous
|
||||||
|
- **Background Tasks**: Long-running operations don't block request handling
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- **CORS**: Configured in main application
|
||||||
|
- **Environment Variables**: Use `.env` file, never commit secrets
|
||||||
|
- **Database Authentication**: MongoDB requires authentication
|
||||||
|
- **Input Validation**: Pydantic models validate all inputs
|
||||||
|
- **Error Handling**: Sensitive information not exposed in error messages
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Database Connection Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test MongoDB connection (update with your connection string)
|
||||||
|
mongosh mongodb://admin:password123@mongodb-host:27017/?authSource=admin
|
||||||
|
|
||||||
|
# Check if MongoDB is accessible from the container
|
||||||
|
docker-compose exec monolith ping mongodb-host
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis Connection Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test Redis connection (update with your connection string)
|
||||||
|
redis-cli -h redis-host ping
|
||||||
|
|
||||||
|
# Check if Redis is accessible from the container
|
||||||
|
docker-compose exec monolith ping redis-host
|
||||||
|
```
|
||||||
|
|
||||||
|
### Application Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
docker-compose logs monolith
|
||||||
|
|
||||||
|
# Verify environment variables
|
||||||
|
docker-compose exec monolith env | grep MONGO
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Your License Here]
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
[Your Contributing Guidelines Here]
|
||||||
41
monolith/docker-compose.yml
Normal file
41
monolith/docker-compose.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Modular Monolith Application
|
||||||
|
monolith:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: energy-dashboard-monolith
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
# MongoDB Configuration (external deployment)
|
||||||
|
- MONGO_URL=${MONGO_URL}
|
||||||
|
|
||||||
|
# Redis Configuration (external deployment, optional)
|
||||||
|
- REDIS_URL=${REDIS_URL}
|
||||||
|
- REDIS_ENABLED=${REDIS_ENABLED:-false}
|
||||||
|
|
||||||
|
# FTP Configuration
|
||||||
|
- FTP_SA4CPS_HOST=${FTP_SA4CPS_HOST:-ftp.sa4cps.pt}
|
||||||
|
- FTP_SA4CPS_PORT=${FTP_SA4CPS_PORT:-21}
|
||||||
|
- FTP_SA4CPS_USERNAME=${FTP_SA4CPS_USERNAME}
|
||||||
|
- FTP_SA4CPS_PASSWORD=${FTP_SA4CPS_PASSWORD}
|
||||||
|
- FTP_SA4CPS_REMOTE_PATH=${FTP_SA4CPS_REMOTE_PATH:-/SLGs/}
|
||||||
|
- FTP_CHECK_INTERVAL=${FTP_CHECK_INTERVAL:-21600}
|
||||||
|
- FTP_SKIP_INITIAL_SCAN=${FTP_SKIP_INITIAL_SCAN:-true}
|
||||||
|
|
||||||
|
# Application Settings
|
||||||
|
- DEBUG=${DEBUG:-false}
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- energy-network
|
||||||
|
volumes:
|
||||||
|
- ./src:/app/src # Mount source code for development
|
||||||
|
|
||||||
|
networks:
|
||||||
|
energy-network:
|
||||||
|
driver: bridge
|
||||||
|
name: energy-network
|
||||||
28
monolith/requirements.txt
Normal file
28
monolith/requirements.txt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# FastAPI and ASGI server
|
||||||
|
fastapi==0.104.1
|
||||||
|
uvicorn[standard]==0.24.0
|
||||||
|
python-multipart==0.0.6
|
||||||
|
|
||||||
|
# Database drivers
|
||||||
|
motor==3.3.2 # Async MongoDB
|
||||||
|
redis[hiredis]==5.0.1 # Redis with hiredis for better performance
|
||||||
|
|
||||||
|
# Data validation and settings
|
||||||
|
pydantic==2.5.0
|
||||||
|
pydantic-settings==2.1.0
|
||||||
|
|
||||||
|
# Async HTTP client
|
||||||
|
aiohttp==3.9.1
|
||||||
|
|
||||||
|
# WebSockets
|
||||||
|
websockets==12.0
|
||||||
|
|
||||||
|
# Data processing
|
||||||
|
pandas==2.1.4
|
||||||
|
numpy==1.26.2
|
||||||
|
|
||||||
|
# FTP support
|
||||||
|
ftputil==5.0.4
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
python-dateutil==2.8.2
|
||||||
Reference in New Issue
Block a user