# Gateway Service Architecture Documentation This document describes the structure, design patterns, and key components of the two service architectures in the gateway: 1. **`modules/serviceCenter`** — the new service center (context-based DI, RBAC-aware) 2. **`modules/services`** — the legacy services hub (monolithic hub, eager loading) --- ## 1. `modules/serviceCenter` — New Service Center ### 1.1 File/Folder Structure ``` modules/serviceCenter/ ├── __init__.py # Public API: getService, preWarm, registerServiceObjects, can_access_service ├── context.py # ServiceCenterContext dataclass ├── registry.py # Service definitions (CORE_SERVICES, IMPORTABLE_SERVICES, RBAC) ├── resolver.py # Resolution logic, dependency injection, legacy fallback ├── core/ # Core services (internal building blocks, no RBAC) │ ├── __init__.py │ ├── serviceUtils/ │ │ └── mainServiceUtils.py │ ├── serviceSecurity/ │ │ └── mainServiceSecurity.py │ └── serviceStreaming/ │ └── mainServiceStreaming.py └── services/ # Feature-facing importable services (RBAC-protected) ├── __init__.py ├── serviceAi/ ├── serviceBilling/ ├── serviceChat/ ├── serviceExtraction/ ├── serviceGeneration/ ├── serviceMessaging/ ├── serviceNeutralization/ ├── serviceSharepoint/ ├── serviceTicket/ └── serviceWeb/ ``` **Design distinction:** - **Core services** — Internal utilities (utils, security, streaming). Never directly requested by features. No RBAC. Security and streaming are fully migrated; legacy hub delegates to service center. - **Importable services** — Feature-facing. Have `objectKey` for RBAC (e.g. `service.web`, `service.extraction`). --- ### 1.2 Service Registration and Discovery **Registration:** Services are declared statically in `registry.py`. - **CORE_SERVICES**: Internal services with `module`, `class`, `dependencies`. - **IMPORTABLE_SERVICES**: Feature-facing services with `module`, `class`, `dependencies`, `objectKey`, `label`. - **SERVICE_RBAC_OBJECTS**: Derived from IMPORTABLE_SERVICES for catalog registration. **Discovery:** No dynamic discovery. All services are explicitly listed in the registry. Adding a service requires editing `registry.py`. ```python # Example from registry.py IMPORTABLE_SERVICES = { "ai": { "module": "modules.serviceCenter.services.serviceAi.mainServiceAi", "class": "AiService", "dependencies": ["chat", "utils", "extraction", "billing"], "objectKey": "service.ai", "label": {"en": "AI", "de": "KI", "fr": "IA"}, }, # ... } ``` --- ### 1.3 Dependency Injection and Factory Patterns **Constructor pattern:** Services receive two arguments from the resolver: 1. `context: ServiceCenterContext` — user, mandate_id, feature_instance_id, workflow 2. `get_service: Callable[[str], Any]` — function to resolve other services by key ```python # Service Center service constructor def __init__(self, context, get_service: Callable[[str], Any]): self._context = context self._get_service = get_service ``` **Dependency resolution:** - The resolver (`resolver.py`) builds a `get_service` callable that recursively resolves dependencies. - Dependencies are declared in the registry (e.g. `"dependencies": ["chat", "utils", "extraction", "billing"]`). - Circular dependencies are detected and raise `RuntimeError`. - Resolution is cached per `(user, mandate_id, feature_instance_id)` + `key`. **Legacy fallback:** If a service fails to load from the service center, the resolver falls back to the legacy `Services` hub when `legacy_hub` is provided. --- ### 1.4 Main Entry Points and Usage Patterns | Entry Point | Purpose | |-------------|---------| | `getService(key, context, legacy_hub=None)` | Resolve a service by key for the given context | | `preWarm(service_keys=None)` | Pre-load service modules at startup (called in `app.py` lifespan) | | `registerServiceObjects(catalogService)` | Register service RBAC objects (called via `registerAllFeaturesInCatalog`) | | `can_access_service(user, rbac, service_key, ...)` | RBAC check for service access | | `ServiceCenterContext(user, mandate_id, feature_instance_id, workflow)` | Context dataclass | **Typical usage (chatbot feature):** ```python from modules.serviceCenter import getService from modules.serviceCenter.context import ServiceCenterContext ctx = ServiceCenterContext(user=user, mandate_id=mandateId, feature_instance_id=featureInstanceId, workflow=workflow) ai_service = getService("ai", ctx, legacy_hub=None) chat_service = getService("chat", ctx, legacy_hub=None) ``` **Feature-level hub (e.g. chatbot):** - `getChatbotServices()` builds a lightweight hub with only required services. - Uses `REQUIRED_SERVICES` to resolve only `chat`, `ai`, `billing`, `streaming`. - Returns a `_ChatbotServiceHub` object with `chat`, `ai`, `billing`, `streaming`, `interfaceDbComponent`, etc. --- ### 1.5 Initialization and Bootstrapping 1. **`app.py` lifespan:** - `registerAllFeaturesInCatalog(catalogService)` → calls `registerServiceObjects(catalogService)` for service RBAC objects - `preWarm()` — imports all service modules to avoid first-request latency 2. **`registerAllFeaturesInCatalog` (modules/system/registry.py):** - Registers system RBAC objects - Registers service center RBAC objects via `registerServiceObjects` - Registers feature RBAC objects 3. **First request:** - `getService(key, ctx)` → `resolve()` loads module, instantiates class with `(context, get_service)`, caches instance --- ## 2. `modules/services` — Legacy Services Hub ### 2.1 File/Folder Structure ``` modules/services/ ├── __init__.py # Services class, getInterface(), PublicService, _loadFeatureInterfaces, _loadFeatureServices ├── serviceAi/ │ └── mainServiceAi.py ├── serviceBilling/ │ └── mainServiceBilling.py ├── serviceChat/ │ └── mainServiceChat.py ├── serviceExtraction/ │ ├── extractors/ │ ├── chunking/ │ ├── merging/ │ ├── subRegistry.py │ ├── subPipeline.py │ └── mainServiceExtraction.py ├── serviceGeneration/ │ ├── paths/ │ ├── renderers/ │ └── mainServiceGeneration.py ├── serviceMessaging/ │ └── mainServiceMessaging.py ├── serviceNormalization/ │ └── mainServiceNormalization.py ├── serviceSharepoint/ │ └── mainServiceSharepoint.py ├── serviceStreaming/ │ ├── eventManager.py │ ├── helpers.py │ └── mainServiceStreaming.py ├── serviceTicket/ │ └── mainServiceTicket.py ├── serviceUtils/ │ └── mainServiceUtils.py ├── serviceWeb/ │ └── mainServiceWeb.py └── serviceSecurity/ └── mainServiceSecurity.py ``` **No core vs. services split.** All services live in flat `serviceX/` directories. --- ### 2.2 Service Registration and Discovery **Registration:** Services are **eagerly loaded** in `Services.__init__()` via hardcoded imports. No registry file. **Discovery:** - **Shared services:** Loaded explicitly in `__init__` from `modules/services/serviceX/mainServiceX.py`. - **Feature services:** Discovered dynamically via `_loadFeatureServices()` — scans `modules/features/*/service*/mainService*.py` and instantiates classes ending with `"Service"`. ```python # Shared services — hardcoded in Services.__init__ from .serviceSharepoint.mainServiceSharepoint import SharepointService self.sharepoint = PublicService(SharepointService(self)) from .serviceChat.mainServiceChat import ChatService self.chat = PublicService(ChatService(self)) # ... etc. ``` --- ### 2.3 Dependency Injection / Factory Patterns **Constructor pattern:** Services receive the entire `Services` hub as their single dependency. ```python # Legacy service constructor def __init__(self, services): self.services = services ``` **No explicit dependency graph.** Services access other services via `self.services.` (e.g. `self.services.interfaceDbComponent`, `self.services.extraction`). All services are loaded at construction time. **PublicService proxy:** Services are wrapped in `PublicService(target, functionsOnly=True)` to expose only callable, non-private attributes. Reduces accidental access to internal state. **BillingService:** Uses a separate factory `getService(currentUser, mandateId, featureInstanceId, featureCode)` and a module-level cache. Not integrated with the hub’s constructor pattern. --- ### 2.4 Main Entry Points and Usage Patterns | Entry Point | Purpose | |-------------|---------| | `getInterface(user, workflow, mandateId, featureInstanceId)` | Returns a `Services` instance | | `Services` | Central hub with all services and interfaces | **Typical usage:** ```python from modules.services import getInterface as getServices services = getServices(user, workflow, mandateId=mandateId, featureInstanceId=featureInstanceId) ai = services.ai extraction = services.extraction ``` **Interfaces loaded at construction:** - `interfaceDbApp`, `interfaceDbComponent`, `interfaceDbChat`, `rbac` - Plus dynamically loaded `interfaceFeature*` from feature containers --- ### 2.5 Initialization and Bootstrapping 1. **No startup bootstrap** — services load on first `getInterface()` call. 2. **Construction flow:** - `getInterface(user, ...)` → `Services(user, ...)` - `Services.__init__`: - Loads DB interfaces (`interfaceDbApp`, `interfaceDbComponent`, `interfaceDbChat`) - Instantiates all shared services (sharepoint, ticket, chat, utils, security, streaming, ai, extraction, generation, web) - Calls `_loadFeatureInterfaces()` — discovers `interfaceFeature*.py` in features - Calls `_loadFeatureServices()` — discovers `service*/mainService*.py` in features, overrides hub attributes 3. **Feature services:** If a feature defines `serviceAi/mainServiceAi.py`, it overrides `services.ai`. Shared `serviceAi` is only used when no feature override exists. --- ## 3. Side-by-Side Comparison | Aspect | Service Center | Legacy Services | |--------|----------------|-----------------| | **Location** | `modules/serviceCenter/` | `modules/services/` | | **Entry point** | `getService(key, context, legacy_hub)` | `getInterface(user, ...)` → `Services` | | **Constructor** | `(context, get_service)` | `(services)` — full hub | | **Context** | `ServiceCenterContext` (user, mandate_id, feature_instance_id, workflow) | Full `Services` with interfaces | | **Dependencies** | Declared in registry, resolved lazily via `get_service("key")` | Via `self.services.` | | **Loading** | Lazy (on first `getService`), cached per context | Eager (all at construction) | | **RBAC** | Per-service `objectKey` in registry, `can_access_service()` | Shared via hub `.rbac` | | **Feature services** | Not applicable — features use `getService(key, ctx)` | Discovered via `_loadFeatureServices()` | | **Pre-warm** | `preWarm()` in app lifespan | None | | **Bootstrap** | `registerServiceObjects()` via `registerAllFeaturesInCatalog` | None | --- ## 4. Coexistence and Migration - **Service center** can fall back to **legacy hub** when `legacy_hub` is passed to `getService`. - **Chatbot** uses service center via `getChatbotServices()` and does not use the legacy hub. - **Billing, routes, teamsbot, commcoach, etc.** still use `modules.services` (e.g. `getInterface`, `getService` from `serviceBilling`). - **`ServiceCenterContext`** is used when calling `getService`. Features that pass `workflow=None` use a placeholder workflow for billing/featureCode. - Migration plan: `docs/SERVICE_CENTER_MIGRATION_PLAN.md`. --- ## 5. Service Center Resolver Flow ``` getService("ai", ctx, legacy_hub) → resolve("ai", ctx, cache, resolving, legacy_hub) → Check cache (cache_key = user_mandate_feature_ai) → If cache hit: return cached instance → If miss: → _load_service_class("modules.serviceCenter.services.serviceAi.mainServiceAi", "AiService") → Resolve dependencies: chat, utils, extraction, billing (recursive resolve) → instance = AiService(ctx, get_service) → cache[cache_key] = instance → return instance → On ImportError/ModuleNotFoundError: _get_from_legacy(legacy_hub, "ai") if legacy_hub ``` --- ## 6. Key Files Reference | File | Purpose | |------|---------| | `serviceCenter/registry.py` | Service definitions, dependency graph, RBAC keys | | `serviceCenter/resolver.py` | Resolution logic, caching, legacy fallback | | `serviceCenter/context.py` | `ServiceCenterContext` dataclass | | `serviceCenter/__init__.py` | `getService`, `preWarm`, `registerServiceObjects`, `can_access_service` | | `services/__init__.py` | `Services` class, `getInterface`, `PublicService`, feature discovery | | `system/registry.py` | `registerAllFeaturesInCatalog` (calls `registerServiceObjects`) | | `app.py` | Lifespan: `preWarm()`, `registerAllFeaturesInCatalog()` | | `features/chatbot/mainChatbot.py` | Example: `getChatbotServices()` using service center |