Initial microservices setup from artifacts

This commit is contained in:
Urtzi Alfaro
2025-07-17 13:09:24 +02:00
commit 347ff51bd7
200 changed files with 9559 additions and 0 deletions

View File

View File

@@ -0,0 +1,52 @@
"""
Gateway configuration
"""
import os
from typing import List, Dict
from pydantic import BaseSettings
class Settings(BaseSettings):
"""Application settings"""
# Basic settings
APP_NAME: str = "Bakery Forecasting Gateway"
VERSION: str = "1.0.0"
DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
# CORS settings
CORS_ORIGINS: List[str] = os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:3001").split(",")
# Service URLs
AUTH_SERVICE_URL: str = os.getenv("AUTH_SERVICE_URL", "http://auth-service:8000")
TRAINING_SERVICE_URL: str = os.getenv("TRAINING_SERVICE_URL", "http://training-service:8000")
FORECASTING_SERVICE_URL: str = os.getenv("FORECASTING_SERVICE_URL", "http://forecasting-service:8000")
DATA_SERVICE_URL: str = os.getenv("DATA_SERVICE_URL", "http://data-service:8000")
TENANT_SERVICE_URL: str = os.getenv("TENANT_SERVICE_URL", "http://tenant-service:8000")
NOTIFICATION_SERVICE_URL: str = os.getenv("NOTIFICATION_SERVICE_URL", "http://notification-service:8000")
# Redis settings
REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/6")
# Rate limiting
RATE_LIMIT_REQUESTS: int = int(os.getenv("RATE_LIMIT_REQUESTS", "100"))
RATE_LIMIT_WINDOW: int = int(os.getenv("RATE_LIMIT_WINDOW", "60"))
# JWT settings
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production")
JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256")
@property
def SERVICES(self) -> Dict[str, str]:
"""Service registry"""
return {
"auth": self.AUTH_SERVICE_URL,
"training": self.TRAINING_SERVICE_URL,
"forecasting": self.FORECASTING_SERVICE_URL,
"data": self.DATA_SERVICE_URL,
"tenant": self.TENANT_SERVICE_URL,
"notification": self.NOTIFICATION_SERVICE_URL
}
settings = Settings()

View File

@@ -0,0 +1,122 @@
"""
Service discovery for microservices
"""
import asyncio
import logging
from typing import Dict, List, Optional
import httpx
import redis.asyncio as redis
from datetime import datetime, timedelta
from app.core.config import settings
logger = logging.getLogger(__name__)
class ServiceDiscovery:
"""Service discovery and health checking"""
def __init__(self):
self.redis_client = redis.from_url(settings.REDIS_URL)
self.services = settings.SERVICES
self.health_check_interval = 30 # seconds
self.health_check_task = None
async def initialize(self):
"""Initialize service discovery"""
logger.info("Initializing service discovery")
# Start health check task
self.health_check_task = asyncio.create_task(self._health_check_loop())
# Initial health check
await self._check_all_services()
async def cleanup(self):
"""Cleanup service discovery"""
if self.health_check_task:
self.health_check_task.cancel()
try:
await self.health_check_task
except asyncio.CancelledError:
pass
await self.redis_client.close()
async def get_service_url(self, service_name: str) -> Optional[str]:
"""Get service URL"""
return self.services.get(service_name)
async def get_healthy_services(self) -> List[str]:
"""Get list of healthy services"""
healthy_services = []
for service_name in self.services:
is_healthy = await self._is_service_healthy(service_name)
if is_healthy:
healthy_services.append(service_name)
return healthy_services
async def _health_check_loop(self):
"""Continuous health check loop"""
while True:
try:
await self._check_all_services()
await asyncio.sleep(self.health_check_interval)
except asyncio.CancelledError:
break
except Exception as e:
logger.error(f"Health check error: {e}")
await asyncio.sleep(self.health_check_interval)
async def _check_all_services(self):
"""Check health of all services"""
for service_name, service_url in self.services.items():
try:
is_healthy = await self._check_service_health(service_url)
await self._update_service_health(service_name, is_healthy)
except Exception as e:
logger.error(f"Health check failed for {service_name}: {e}")
await self._update_service_health(service_name, False)
async def _check_service_health(self, service_url: str) -> bool:
"""Check individual service health"""
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get(f"{service_url}/health")
return response.status_code == 200
except Exception as e:
logger.warning(f"Service health check failed: {e}")
return False
async def _update_service_health(self, service_name: str, is_healthy: bool):
"""Update service health status in Redis"""
try:
key = f"service_health:{service_name}"
value = {
"healthy": is_healthy,
"last_check": datetime.utcnow().isoformat(),
"url": self.services[service_name]
}
await self.redis_client.hset(key, mapping=value)
await self.redis_client.expire(key, 300) # 5 minutes TTL
except Exception as e:
logger.error(f"Failed to update service health for {service_name}: {e}")
async def _is_service_healthy(self, service_name: str) -> bool:
"""Check if service is healthy from Redis cache"""
try:
key = f"service_health:{service_name}"
health_data = await self.redis_client.hgetall(key)
if not health_data:
return False
return health_data.get(b'healthy', b'false').decode() == 'True'
except Exception as e:
logger.error(f"Failed to check service health for {service_name}: {e}")
return False