File size: 7,167 Bytes
bcc8074
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
"""
Base Service Infrastructure

Provides the foundation for plug-and-play services in the API gateway.
All services (auth, credit, gemini, etc.) extend this base infrastructure.

Core Components:
- BaseService: Abstract base class for all services
- ServiceConfig: Configuration container
- ServiceRegistry: Global registry for service discovery
- MiddlewareProtocol: Type definition for middleware functions

Usage:
    class MyService(BaseService):
        @classmethod
        def register(cls, **config):
            # Service-specific registration
            pass
        
        @classmethod
        def get_middleware(cls):
            # Return middleware function if needed
            return MyMiddleware()
"""

import logging
from abc import ABC, abstractmethod
from typing import Dict, Type, Optional, Callable, Any
from starlette.middleware.base import BaseHTTPMiddleware

logger = logging.getLogger(__name__)


class ServiceConfig:
    """
    Base configuration container for services.
    
    Services can extend this to add their specific configuration.
    """
    
    def __init__(self, **kwargs):
        """Initialize configuration with arbitrary key-value pairs."""
        self._config = kwargs
    
    def get(self, key: str, default: Any = None) -> Any:
        """Get configuration value."""
        return self._config.get(key, default)
    
    def set(self, key: str, value: Any) -> None:
        """Set configuration value."""
        self._config[key] = value
    
    def __getitem__(self, key: str) -> Any:
        """Dictionary-style access."""
        return self._config[key]
    
    def __setitem__(self, key: str, value: Any) -> None:
        """Dictionary-style assignment."""
        self._config[key] = value
    
    def __contains__(self, key: str) -> bool:
        """Check if key exists."""
        return key in self._config


class BaseService(ABC):
    """
    Abstract base class for all plug-and-play services.
    
    Services must implement:
    - register(): Register service configuration at startup
    - get_middleware(): Return middleware if service needs request interception
    - on_shutdown(): Cleanup on app shutdown
    """
    
    # Service name (override in subclass)
    SERVICE_NAME: str = "base_service"
    
    # Service configuration
    _config: Optional[ServiceConfig] = None
    
    # Registration state
    _registered: bool = False
    
    @classmethod
    @abstractmethod
    def register(cls, **config) -> None:
        """
        Register service configuration at application startup.
        
        Args:
            **config: Service-specific configuration parameters
        
        Raises:
            RuntimeError: If service is already registered
        """
        if cls._registered:
            raise RuntimeError(f"{cls.SERVICE_NAME} is already registered")
        
        cls._config = ServiceConfig(**config)
        cls._registered = True
        
        logger.info(f"✅ {cls.SERVICE_NAME} registered successfully")
    
    @classmethod
    def get_middleware(cls) -> Optional[BaseHTTPMiddleware]:
        """
        Return middleware instance if service needs request interception.
        
        Returns:
            Middleware instance or None if service doesn't need middleware
        """
        return None
    
    @classmethod
    def on_shutdown(cls) -> None:
        """
        Cleanup hook called during application shutdown.
        
        Override this to perform cleanup (close connections, save state, etc.)
        """
        pass
    
    @classmethod
    def is_registered(cls) -> bool:
        """Check if service has been registered."""
        return cls._registered
    
    @classmethod
    def assert_registered(cls) -> None:
        """
        Assert that service has been registered.
        
        Raises:
            RuntimeError: If service is not registered
        """
        if not cls._registered:
            raise RuntimeError(
                f"{cls.SERVICE_NAME} is not registered. "
                f"Call {cls.SERVICE_NAME}.register() at application startup."
            )
    
    @classmethod
    def get_config(cls) -> ServiceConfig:
        """
        Get service configuration.
        
        Returns:
            ServiceConfig instance
        
        Raises:
            RuntimeError: If service is not registered
        """
        cls.assert_registered()
        return cls._config


class ServiceRegistry:
    """
    Global registry for service discovery and management.
    
    Tracks all registered services and provides lookup functionality.
    """
    
    _services: Dict[str, Type[BaseService]] = {}
    
    @classmethod
    def register_service(cls, service_class: Type[BaseService]) -> None:
        """
        Register a service class in the global registry.
        
        Args:
            service_class: Service class to register
        """
        service_name = service_class.SERVICE_NAME
        
        if service_name in cls._services:
            logger.warning(f"Service '{service_name}' already registered, overwriting")
        
        cls._services[service_name] = service_class
        logger.debug(f"Registered service: {service_name}")
    
    @classmethod
    def get_service(cls, service_name: str) -> Optional[Type[BaseService]]:
        """
        Get a service class by name.
        
        Args:
            service_name: Name of the service to retrieve
        
        Returns:
            Service class or None if not found
        """
        return cls._services.get(service_name)
    
    @classmethod
    def get_all_services(cls) -> Dict[str, Type[BaseService]]:
        """
        Get all registered services.
        
        Returns:
            Dictionary mapping service names to service classes
        """
        return cls._services.copy()
    
    @classmethod
    def get_all_middleware(cls) -> list:
        """
        Get middleware from all registered services.
        
        Returns:
            List of middleware instances in registration order
        """
        middleware_list = []
        
        for service_name, service_class in cls._services.items():
            if service_class.is_registered():
                middleware = service_class.get_middleware()
                if middleware:
                    middleware_list.append(middleware)
                    logger.debug(f"Added middleware from service: {service_name}")
        
        return middleware_list
    
    @classmethod
    def shutdown_all(cls) -> None:
        """
        Call shutdown hooks for all registered services.
        """
        logger.info("Shutting down all services...")
        
        for service_name, service_class in cls._services.items():
            try:
                service_class.on_shutdown()
                logger.debug(f"Shutdown complete: {service_name}")
            except Exception as e:
                logger.error(f"Error shutting down {service_name}: {e}")
        
        logger.info("All services shut down")


__all__ = [
    'BaseService',
    'ServiceConfig',
    'ServiceRegistry',
]