# ServiceLocator - Dependency Injection Container > **Advanced DI container với global singletons và scoped lifecycle management** ## Overview `ServiceLocator` quản lý dependencies và resource lifecycle trong application. Hỗ trợ: - **Global Services**: Singletons tồn tại suốt application lifetime - **Scoped Services**: Resources gắn với tag (task UUID), tự động cleanup - **Thread-safe**: QMutex cho all operations - **Auto cleanup**: Gọi `cleanup()`/`close()`/`dispose()` khi release scope ## API Reference ### Global Services **Register:** ```python from core import QtAppContext ctx = QtAppContext.globalInstance() myService = MyService() ctx.registerService('my_service', myService) ``` **Retrieve:** ```python myService = ctx.getService('my_service') myService = ctx.getService('missing', default=None) ``` ### Scoped Services **Register:** ```python taskId = str(uuid.uuid4()) # Or self.uuid in AbstractTask resource = ChromeBrowserService() ctx.registerScopedService(taskId, resource) ``` **Retrieve all under tag:** ```python # Internal use only - normally not needed resources = ctx._services.getScoped(taskId) ``` **Release scope:** ```python ctx.releaseScope(taskId) # Cleanup all resources under tag ``` **Cleanup priority:** 1. `cleanup()` method (highest priority) 2. `close()` method 3. `dispose()` method ## Usage Examples ### Global Service Registration ```python from core import QtAppContext class DatabaseService: def __init__(self, config): self.connection = None self.config = config def connect(self): # Connect to database pass def query(self, sql): # Execute query pass # Bootstrap ctx = QtAppContext.globalInstance() ctx.bootstrap() # Register global service dbService = DatabaseService(ctx.config) dbService.connect() ctx.registerService('database', dbService) # Access from anywhere db = ctx.getService('database') results = db.query('SELECT * FROM users') ``` ### Scoped Service with Task ```python from core import QtAppContext from core.taskSystem import AbstractTask class ChromeBrowserService: def __init__(self): from selenium import webdriver self.driver = webdriver.Chrome() def navigate(self, url): self.driver.get(url) def cleanup(self): """Called automatically by ServiceLocator.releaseScope()""" if self.driver: self.driver.quit() self.driver = None class BrowserTask(AbstractTask): def handle(self): ctx = QtAppContext.globalInstance() taskId = self.uuid # Create and register scoped service browser = ChromeBrowserService() ctx.registerScopedService(taskId, browser) try: # Use browser browser.navigate('https://example.com') # Do scraping... if self.isStopped(): return finally: # Auto cleanup: calls browser.cleanup() ctx.releaseScope(taskId) ``` ### Multiple Scoped Resources ```python class TempFileHandler: def __init__(self): self.files = [] def createTemp(self, name): import tempfile f = tempfile.NamedTemporaryFile(delete=False) self.files.append(f.name) return f def cleanup(self): import os for f in self.files: try: os.remove(f) except: pass class ApiSession: def __init__(self): import requests self.session = requests.Session() def get(self, url): return self.session.get(url) def close(self): self.session.close() class ComplexTask(AbstractTask): def handle(self): ctx = QtAppContext.globalInstance() taskId = self.uuid # Register multiple scoped resources browser = ChromeBrowserService() tempFiles = TempFileHandler() apiSession = ApiSession() ctx.registerScopedService(taskId, browser) ctx.registerScopedService(taskId, tempFiles) ctx.registerScopedService(taskId, apiSession) try: # Use all resources browser.navigate('https://example.com') tempFile = tempFiles.createTemp('data.json') response = apiSession.get('https://api.example.com/data') # Process... finally: # Cleanup all: browser.cleanup(), tempFiles.cleanup(), apiSession.close() ctx.releaseScope(taskId) ``` ### Cleanup Method Priority ```python class MyResource: # Priority 1: cleanup() - preferred def cleanup(self): print('cleanup() called') # Cleanup logic # Priority 2: close() - if cleanup() not found def close(self): print('close() called') # Cleanup logic # Priority 3: dispose() - if neither cleanup() nor close() found def dispose(self): print('dispose() called') # Cleanup logic # ServiceLocator will call cleanup() first ctx.registerScopedService(taskId, MyResource()) ctx.releaseScope(taskId) # Calls cleanup() ``` ### Avoiding Duplicate Registration ```python # ServiceLocator prevents duplicate registration of same object browser = ChromeBrowserService() ctx.registerScopedService(taskId, browser) ctx.registerScopedService(taskId, browser) # Ignored - already registered # But different instances are allowed browser2 = ChromeBrowserService() ctx.registerScopedService(taskId, browser2) # OK - different instance ``` ## Architecture ```mermaid graph TB QtAppContext --> ServiceLocator ServiceLocator --> GlobalRegistry["Global Registry
Dict[str, Any]"] ServiceLocator --> ScopedRegistry["Scoped Registry
Dict[str, List[Any]]"] GlobalRegistry --> ConfigService[Config] GlobalRegistry --> PublisherService[Publisher] GlobalRegistry --> CustomServices[Custom Services] ScopedRegistry --> Task1[Task UUID 1] ScopedRegistry --> Task2[Task UUID 2] Task1 --> Browser1[Browser Instance] Task1 --> TempFiles1[Temp Files] Task2 --> Browser2[Browser Instance] Task2 --> ApiSession2[API Session] releaseScope[releaseScope] -->|Cleanup| Task1 releaseScope -->|Cleanup| Task2 style ServiceLocator fill:#fff4e1 style GlobalRegistry fill:#e1f5ff style ScopedRegistry fill:#f3e5f5 style releaseScope fill:#ffcdd2 ``` ## Global vs Scoped Services ### Global Services (Singletons) **Characteristics:** - Tồn tại suốt application lifetime - Shared across all components - Không tự động cleanup **Use cases:** - Config - Publisher - NetworkManager - TaskManager - Database connections - API clients - Cache managers **Example:** ```python # Register once during bootstrap ctx.bootstrap() dbService = DatabaseService(ctx.config) ctx.registerService('database', dbService) # Access anywhere db = ctx.getService('database') ``` ### Scoped Services (Task-specific) **Characteristics:** - Gắn với specific tag (task UUID) - Tự động cleanup khi releaseScope() - Isolated per task **Use cases:** - Browser instances (Selenium, Playwright) - Temporary file handlers - Task-specific API sessions - Any resource cần cleanup sau task **Example:** ```python # Register per task taskId = self.uuid browser = ChromeBrowserService() ctx.registerScopedService(taskId, browser) try: # Use browser pass finally: ctx.releaseScope(taskId) # Auto cleanup ``` ## Best Practices ### ✅ DO ```python # Use QtAppContext as facade ctx = QtAppContext.globalInstance() ctx.registerService('my_service', myService) ctx.registerScopedService(taskId, resource) # Always release scoped services try: # Use resource pass finally: ctx.releaseScope(taskId) # Implement cleanup methods class MyResource: def cleanup(self): # Cleanup logic pass # Register global services during bootstrap ctx.bootstrap() ctx.registerService('database', DatabaseService()) # Use descriptive service names ctx.registerService('user_repository', UserRepository()) ctx.registerService('email_service', EmailService()) ``` ### ❌ DON'T ```python # Don't access ServiceLocator directly from core.ServiceLocator import ServiceLocator locator = ServiceLocator() # Wrong! Use QtAppContext # Don't forget to release scoped services ctx.registerScopedService(taskId, browser) # ... use browser ... # Missing: ctx.releaseScope(taskId) # Memory leak! # Don't register scoped services without cleanup class BadResource: pass # No cleanup()/close()/dispose() method ctx.registerScopedService(taskId, BadResource()) # Will log warning # Don't use scoped services for singletons ctx.registerScopedService('global', ConfigService()) # Wrong! Use registerService # Don't register services before bootstrap ctx.registerService('my_service', MyService()) # Wrong order ctx.bootstrap() ``` ## Thread Safety - ✅ All operations thread-safe (QMutex) - ✅ Safe to register/retrieve from multiple threads - ✅ Safe to release scopes concurrently - ⚠️ Cleanup methods called sequentially per scope ## Cleanup Mechanism ### Cleanup Order ```python ctx.releaseScope(taskId) ``` **For each instance under tag:** 1. Check for `cleanup()` method → Call if exists 2. Else check for `close()` method → Call if exists 3. Else check for `dispose()` method → Call if exists 4. Remove reference from registry **Error handling:** - Exceptions in cleanup methods are logged but don't stop cleanup of other instances - All instances are cleaned up even if some fail ### Example with Errors ```python class ResourceA: def cleanup(self): raise Exception('Cleanup failed!') class ResourceB: def cleanup(self): print('ResourceB cleaned up') ctx.registerScopedService(taskId, ResourceA()) ctx.registerScopedService(taskId, ResourceB()) ctx.releaseScope(taskId) # Output: # ERROR: Error cleaning up instance ResourceA: Cleanup failed! # ResourceB cleaned up ``` ## Common Patterns ### Service Factory Pattern ```python class ServiceFactory: @staticmethod def createDatabaseService(config): db = DatabaseService(config) db.connect() return db @staticmethod def createApiClient(config): return ApiClient(config.get('api.base_url')) # Register during bootstrap ctx.bootstrap() ctx.registerService('database', ServiceFactory.createDatabaseService(ctx.config)) ctx.registerService('api_client', ServiceFactory.createApiClient(ctx.config)) ``` ### Resource Pool Pattern ```python class BrowserPool: def __init__(self, size=5): self.pool = [ChromeBrowserService() for _ in range(size)] self.available = list(self.pool) def acquire(self): if self.available: return self.available.pop() return None def release(self, browser): if browser in self.pool: self.available.append(browser) def cleanup(self): for browser in self.pool: browser.cleanup() # Register as global service ctx.registerService('browser_pool', BrowserPool(size=5)) # Use in tasks pool = ctx.getService('browser_pool') browser = pool.acquire() try: # Use browser pass finally: pool.release(browser) ``` ### Lazy Initialization Pattern ```python class LazyService: def __init__(self): self._instance = None @property def instance(self): if self._instance is None: self._instance = ExpensiveService() return self._instance def cleanup(self): if self._instance: self._instance.cleanup() ctx.registerService('lazy_service', LazyService()) # Only initialized when accessed service = ctx.getService('lazy_service').instance ``` ## Related Documentation - [QtAppContext](01-application-context.md) - Application lifecycle - [AbstractTask](13-abstract-task.md) - Task scoping examples - [Common Use Cases](20-common-use-cases.md) - Practical examples ## Troubleshooting **Q: Scoped services not cleaned up** ```python # Ensure cleanup method exists class MyResource: def cleanup(self): # Must have this # Cleanup logic pass ``` **Q: Service not found** ```python service = ctx.getService('missing_service') # Returns None # Use default service = ctx.getService('missing_service', default=DefaultService()) ``` **Q: Memory leak with scoped services** ```python # Always release in finally block try: ctx.registerScopedService(taskId, resource) # Use resource finally: ctx.releaseScope(taskId) # Don't forget! ```