wiki/b-reference/platform/database-architecture.md
2026-04-12 18:32:37 +02:00

270 lines
9.4 KiB
Markdown

<!-- status: canonical -->
<!-- lastReviewed: 2026-04-05 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-05) -->
# Datenbank-Architektur
## Ueberblick
PowerOn verwendet **PostgreSQL** mit einer klaren Trennung: jedes Modul (App, Chat, Billing, Knowledge, Features) hat eine eigene physische Datenbank (`poweron_*`). Datenbank-Zugriffe laufen ueber **Interfaces** (`interfaceDb*.py`), die auf dem zentralen **DatabaseConnector** (`connectorDbPostgre.py`) aufbauen. Datenbanken und Tabellen werden **automatisch** beim ersten Zugriff erzeugt -- kein manuelles Schema-Management noetig.
---
## Architektur-Stack
```
Route / Service
|
v
Interface (interfaceDb*.py)
|-- XxxObjects Klasse
|-- getInterface() Factory (cached)
|-- setUserContext() -> RBAC-Anbindung
|
v
DatabaseConnector (connectorDbPostgre.py)
|-- Auto-Create Database
|-- Auto-Create/Extend Tables (aus Pydantic-Modellen)
|-- CRUD-Operationen (getRecordset, recordCreate, recordUpdate, recordDelete)
|
v
PostgreSQL (+ pgvector Extension)
```
---
## Datenbanken
### Inventar
| Datenbank | Modul / Zweck | Interface |
|-----------|---------------|-----------|
| `poweron_app` | Kernapplikation: UAM, Mandanten, Rollen, RBAC-Regeln, Navigation, Config | `interfaceDbApp.py` |
| `poweron_chat` | Chat-Verlaeufe, Messages, Attachments | `interfaceDbChat.py` |
| `poweron_management` | System-Management, Logs | `interfaceDbManagement.py` |
| `poweron_knowledge` | RAG Knowledge-Store (mit pgvector) | `interfaceDbKnowledge.py` |
| `poweron_billing` | Billing Accounts, Transactions, Usage | `interfaceDbBilling.py` |
| `poweron_billing` | Subscriptions (gleiche DB wie Billing) | `interfaceDbSubscription.py` |
| `poweron_workspace` | AI Workspace Feature-Daten | Feature-Interface |
| `poweron_automation` | Automation v1 Workflows, Runs | Feature-Interface |
| `poweron_automation2` | Automation v2 (Graph-Editor) Workflows, Runs | Feature-Interface |
| `poweron_chatbot` | Chatbot Feature-Daten | Feature-Interface |
| `poweron_trustee` | Trustee Feature-Daten | Feature-Interface |
| `poweron_commcoach` | CommCoach Feature-Daten | Feature-Interface |
| `poweron_neutralization` | Neutralisierungs-Daten | Feature-Interface |
| `poweron_realestate` | RealEstate Feature-Daten | Feature-Interface |
| `poweron_teamsbot` | Teams-Bot Feature-Daten | Feature-Interface |
| `poweron_test` | Test-Datenbank | Nur in Tests |
### Namenskonvention
Feature-Datenbanken folgen dem Pattern `poweron_{featureCode.lower()}`. Der Datenbankname wird hart-codiert in der `_initializeDatabase()` Methode des jeweiligen Interfaces.
### Konfiguration
Verbindungsparameter kommen aus `APP_CONFIG` (config.ini / Environment):
| Key | Beschreibung |
|-----|-------------|
| `DB_HOST` | PostgreSQL Host |
| `DB_PORT` | PostgreSQL Port |
| `DB_USER` | Datenbank-Benutzer |
| `DB_PASSWORD_SECRET` | Passwort (ggf. verschluesselt) |
| `DB_DATABASE` | Default-DB-Name (Standard: `poweron_app`) |
---
## Interface-Pattern
Jedes Interface folgt einem einheitlichen Aufbau:
### XxxObjects Klasse
```python
class AppObjects:
db: DatabaseConnector # Connector zur eigenen DB
rbac: RbacClass # RBAC-Integration
currentUser: dict # Aktueller Benutzer
mandateId: str # Aktueller Mandant
featureInstanceId: str # (nur bei Feature-Interfaces)
```
### _initializeDatabase()
Setzt den Datenbanknamen und erstellt den Connector:
```python
def _initializeDatabase(self):
dbDatabase = "poweron_app" # hart-codiert pro Interface
self.db = DatabaseConnector(
host=APP_CONFIG.get("DB_HOST"),
database=dbDatabase,
user=APP_CONFIG.get("DB_USER"),
password=APP_CONFIG.get("DB_PASSWORD_SECRET"),
port=APP_CONFIG.get("DB_PORT")
)
```
### setUserContext()
Bindet Benutzer und RBAC an die Instanz:
```python
def setUserContext(self, currentUser, mandateId):
self.currentUser = currentUser
self.userId = currentUser["id"]
self.mandateId = mandateId
self.rbac = RbacClass(self.db, dbApp=dbApp)
self.db.updateContext(self.userId)
```
**RBAC-Besonderheit:** `poweron_app` ist Sonderfall (`dbApp=self.db`), weil RBAC-Regeln selbst in `poweron_app` liegen. Alle anderen Interfaces nutzen `getRootDbAppConnector()` um Rules aus der App-DB zu lesen:
```python
dbApp = getRootDbAppConnector()
self.rbac = RbacClass(self.db, dbApp=dbApp)
```
### getInterface() Factory
Cached pro Kontext:
| Interface | Cache-Key | Ergebnis |
|-----------|-----------|----------|
| App | `{mandateId}_{userId}` | `AppObjects` |
| Chat | `{mandateId}_{featureInstanceId}_{userId}` | `ChatObjects` |
| Billing | `{userId}_{mandateId}` | `BillingObjects` |
| Knowledge | `"default"` (Singleton) | `KnowledgeObjects` |
---
## DatabaseConnector
### Auto-Initialisierung
Beim Erstellen eines `DatabaseConnector` (`initDbSystem()`):
```
1. _create_database_if_not_exists()
-> Verbindet sich zu DB "postgres"
-> Prueft pg_database auf Existenz
-> CREATE DATABASE "{name}" falls fehlend
2. _create_tables()
-> Erstellt nur die _system Registry-Tabelle
-> Applikations-Tabellen werden lazy durch Interfaces erzeugt
3. _connect()
-> psycopg2-Verbindung zur Ziel-DB
4. _initializeSystemTable()
-> _ensureTableExists(SystemTable)
```
### Lazy Table Creation
Tabellen werden **bei erstem Zugriff** automatisch aus Pydantic-Modellen erzeugt (`_ensureTableExists(model_class)`):
- **Tabellenname** = Python-Klassenname des Pydantic-Modells (z.B. `Role`, `AccessRule`, `UserMandate`)
- **Spalten** werden aus Modellfeldern abgeleitet
- **JSONB** fuer komplexe Typen (dict, list, Pydantic-Submodelle)
- **vector** fuer pgvector-Felder (Knowledge-Store)
- **Additive Migration**: Wenn die Tabelle existiert, werden fehlende Spalten automatisch hinzugefuegt. Bestehende Spalten werden **nicht** geaendert oder entfernt.
### Connector-Caching
```python
_get_cached_connector(host, database, port)
```
Wiederverwendet einen `DatabaseConnector` pro Datenbank-Identitaet (`host:database:port`). Genutzt von App, Management, Knowledge. Chat und Billing instanziieren direkt (kein Cache).
### userId Context
`userId` wird ueber eine `contextvar` am Connector gesetzt (`updateContext(userId)`). Damit werden System-Felder (`_createdBy`, `_updatedBy`) automatisch befuellt.
---
## RBAC-Integration in DB-Abfragen
Die RBAC-Schicht greift direkt in Datenbank-Abfragen ein:
### Kern-Funktionen in interfaceRbac.py
| Funktion | Zweck |
|----------|-------|
| `getRecordsetWithRBAC()` | Listet Datensaetze mit RBAC-Filter in SQL WHERE |
| `buildRbacWhereClause()` | Uebersetzt Access Levels in SQL-Bedingungen |
| `buildDataObjectKey()` | Baut Keys wie `data.uam.UserInDB` fuer Rule-Lookup |
| `TABLE_NAMESPACE` | Mapping von Modellnamen zu logischen Namespaces |
### Namespace-Mapping
`TABLE_NAMESPACE` ordnet Pydantic-Modellnamen logischen Bereichen zu:
| Namespace | Modelle (Beispiele) |
|-----------|-------------------|
| `uam` | UserInDB, UserMandate, Role, AccessRule |
| `chat` | ChatHistory, ChatMessage |
| `files` | FileRecord, FileFolder |
| `feature.trustee` | TrusteePosition, TrusteeDocument |
| `feature.workspace` | Workspace-spezifische Modelle |
### SQL-Filter nach Access Level
| Level | SQL WHERE |
|-------|-----------|
| `a` (ALL) | Kein Filter |
| `m` (MANDATE) | `mandateId = :mandateId` |
| `o` (OWN) | `_createdBy = :userId` |
| `n` (NONE) | `1=0` (kein Ergebnis) |
---
## System-Felder
Jedes `PowerOnModel` erbt automatisch System-Felder:
| Feld | Beschreibung | Schutz |
|------|-------------|--------|
| `id` | UUID, auto-generiert | Immutable |
| `_createdBy` | userId des Erstellers | Immutable |
| `_createdAt` | Zeitstempel Erstellung | Immutable |
| `_updatedBy` | userId der letzten Aenderung | Auto-gesetzt |
| `_updatedAt` | Zeitstempel letzte Aenderung | Auto-gesetzt |
Felder mit fuehrendem `_` sind fuer Anwendungs-CUD geschuetzt -- der Connector erzwingt das unabhaengig von Access Rules.
---
## Feature-DB-Registrierung
Features registrieren sich **nicht** in einer zentralen DB-Liste. Stattdessen:
1. `registry.py``registerAllFeaturesInCatalog()` laedt Feature-Main-Module
2. Jedes Feature-Main definiert seinen `FEATURE_CODE` und `dbDatabase`
3. Der Datenbank-Name wird in `_initializeDatabase()` des Feature-Interfaces gesetzt
4. Der `DatabaseConnector` erzeugt die DB automatisch beim ersten Zugriff
### Admin-Sicht
Die Admin-DB-Listing-API (`routeSecurityAdmin.py`) wurde am 2026-04-12 entfernt. Datenbank-Diagnostik erfolgt direkt ueber PostgreSQL-Tools oder das System-Dashboard.
---
## Schluessel-Dateien
| Thema | Pfad |
|-------|------|
| PostgreSQL Connector | `gateway/modules/connectors/connectorDbPostgre.py` |
| App-DB Interface | `gateway/modules/interfaces/interfaceDbApp.py` |
| Chat-DB Interface | `gateway/modules/interfaces/interfaceDbChat.py` |
| Management-DB Interface | `gateway/modules/interfaces/interfaceDbManagement.py` |
| Knowledge-DB Interface | `gateway/modules/interfaces/interfaceDbKnowledge.py` |
| Billing-DB Interface | `gateway/modules/interfaces/interfaceDbBilling.py` |
| Subscription Interface | `gateway/modules/interfaces/interfaceDbSubscription.py` |
| RBAC in DB-Schicht | `gateway/modules/interfaces/interfaceRbac.py` |
| Feature-Interface (Template) | `gateway/modules/interfaces/interfaceFeatures.py` |
| Bootstrap / DB-Seed | `gateway/modules/interfaces/interfaceBootstrap.py` |
| DB-Migration Script | `gateway/scripts/script_db_export_migration.py` |
| Datenmodelle (Pydantic) | `gateway/modules/datamodels/` |