gateway/docs/real-estate-feature-integration-guide/03-interfaces.md

845 lines
30 KiB
Markdown

# Schritt 2: Interface erstellen
[← Zurück: Datenmodell erstellen](02-datamodels.md) | [Weiter: Feature-Logik implementieren →](04-feature-logic.md)
## Übersicht: Was sind Interfaces?
**Interfaces** sind aktive Klassen, die den **Datenbankzugriff** implementieren. Sie unterscheiden sich von **Datamodels** (die nur die Datenstruktur definieren):
| **Aspekt** | **Datamodels** | **Interfaces** |
|------------|----------------|----------------|
| **Zweck** | Definiert **WAS** (Datenstruktur) | Implementiert **WIE** (Datenzugriff) |
| **Inhalt** | Pydantic-Modelle mit Feldern und Validierung | Klassen mit CRUD-Methoden (`create`, `get`, `update`, `delete`) |
| **Beispiel** | `class Projekt(BaseModel): ...` | `def createProjekt(...) -> Projekt: ...` |
| **Aktivität** | Passiv (nur Struktur) | Aktiv (führt Operationen aus) |
**Analogie:**
- **Datamodel** = Bauplan (beschreibt das Haus)
- **Interface** = Bauunternehmer (baut das Haus)
---
## Struktur: Real Estate CRUD-Interface
Da das Feature **stateless** arbeitet, benötigen wir nur **ein Interface** für CRUD-Operationen auf Real Estate-Entitäten:
### Real Estate-Datenmodelle → Real Estate CRUD-Interface
**Datamodel:** `datamodelRealEstate.py`
- `Projekt`
- `Parzelle`
- `Dokument`
- `Kanton`, `Gemeinde`, `Land`
- `GeoPolylinie`, `GeoPunkt`
- `Kontext`
- etc.
**Interface:** `interfaceDbRealEstateObjects.py`
- `RealEstateObjects` (Haupt-Interface)
- `RealEstateAccess` (Zugriffskontrolle)
- Methoden: `createProjekt()`, `getParzelle()`, `updateDokument()`, etc.
**Optional:** `interfaceDbRealEstateChatObjects.py` (nur für direkte SQL-Queries)
- `RealEstateChatObjects` - Für direkte Query-Ausführung ohne Session
- Methoden: `executeQuery()` - Führt SQL direkt aus
---
## Warum nur ein Haupt-Interface?
1. **Stateless Design**:
- Keine Session-Verwaltung notwendig
- Direkte CRUD-Operationen auf Real Estate-Modellen
2. **Einfache Architektur**:
- Ein Interface für alle CRUD-Operationen
- Weniger Komplexität, bessere Wartbarkeit
3. **Optionales Query-Interface**:
- Nur für direkte SQL-Queries (stateless)
- Keine Session-Management-Funktionen
---
## Zu erstellende Dateien
### Schritt 2a: Real Estate CRUD-Interface (ERFORDERLICH)
**Zwei separate Dateien** (wie bei anderen Features):
#### Datei 1: `modules/interfaces/interfaceDbRealEstateAccess.py`
**Enthält:**
- `RealEstateAccess` - Zugriffskontrolle für Real Estate-Entitäten
- Methoden: `uam()`, `canModify()`
**Zweck:** Prüft Zugriffsrechte und filtert Daten basierend auf Benutzerprivilegien
#### Datei 2: `modules/interfaces/interfaceDbRealEstateObjects.py`
**Enthält:**
- `RealEstateObjects` - Haupt-Interface für CRUD-Operationen
- `getInterface()` - Factory-Funktion
- Nutzt `RealEstateAccess` aus der Access-Datei
**Zweck:** Verwaltet Real Estate-Entitäten (Projekt, Parzelle, Dokument, etc.)
**Nutzt:**
- `datamodelRealEstate.py` (Projekt, Parzelle, Dokument, etc.)
- `interfaceDbRealEstateAccess.py` (für Zugriffskontrolle)
**Wann benötigt:** Für alle CRUD-Operationen auf Real Estate-Entitäten (z.B. Projekte erstellen/bearbeiten, Parzellen verwalten). Dies ist das Haupt-Interface für das Feature.
---
### Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)
**Eine Datei** für stateless Query-Ausführung:
#### Datei: `modules/interfaces/interfaceDbRealEstateChatObjects.py`
**Enthält:**
- `RealEstateChatObjects` - Interface für direkte SQL-Query-Ausführung
- `getInterface()` - Factory-Funktion
- Methoden: `executeQuery()` - Führt SQL direkt aus (stateless)
**Zweck:** Direkte SQL-Query-Ausführung ohne Session-Management
**Nutzt:**
- `connectorDbPostgre.DatabaseConnector` für direkte SQL-Ausführung
- Keine Chat-Modelle (stateless)
**Wann benötigt:** Nur wenn Sie direkte SQL-Queries ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.
---
## Übersicht: Dateien und ihre Beziehungen
```
┌─────────────────────────────────────────────────────────────┐
│ DATAMODELS (Struktur) │
├─────────────────────────────────────────────────────────────┤
│ datamodelRealEstate.py │
│ ├── Projekt │
│ ├── Parzelle │
│ ├── Dokument │
│ ├── Kanton, Gemeinde, Land │
│ ├── GeoPolylinie, GeoPunkt │
│ ├── Kontext │
│ └── ... │
└─────────────────────────────────────────────────────────────┘
│ nutzt
┌─────────────────────────────────────────────────────────────┐
│ INTERFACES (Zugriff) │
├─────────────────────────────────────────────────────────────┤
│ REAL ESTATE CRUD-INTERFACE (ERFORDERLICH) │
│ │
│ interfaceDbRealEstateAccess.py │
│ └── RealEstateAccess │
│ ├── uam() │
│ └── canModify() │
│ │
│ interfaceDbRealEstateObjects.py │
│ ├── RealEstateObjects │
│ │ ├── createProjekt() │
│ │ ├── getProjekt() │
│ │ ├── updateProjekt() │
│ │ ├── deleteProjekt() │
│ │ ├── createParzelle() │
│ │ ├── getParzelle() │
│ │ └── ... (CRUD für alle Entitäten) │
│ └── getInterface() │
│ └── nutzt RealEstateAccess │
│ │
│ QUERY-INTERFACE (OPTIONAL - nur für direkte SQL) │
│ │
│ interfaceDbRealEstateChatObjects.py │
│ ├── RealEstateChatObjects │
│ │ └── executeQuery() # Direkte SQL-Ausführung │
│ └── getInterface() │
│ └── Keine Access-Klasse (stateless) │
└─────────────────────────────────────────────────────────────┘
```
---
## Interface-Struktur: Access vs. Objects
Jedes Interface besteht aus **zwei Klassen**:
### 1. `*Access` Klasse (Zugriffskontrolle)
**Zweck:** Prüft, wer was sehen/dürfen darf
**Methoden:**
- `uam()` - Filtert Daten basierend auf Benutzerprivilegien
- `canModify()` - Prüft, ob Benutzer ändern darf
**Beispiel:** `RealEstateChatAccess`
### 2. `*Objects` Klasse (Haupt-Interface)
**Zweck:** Führt CRUD-Operationen aus
**Methoden:**
- `create*()` - Erstellt neue Einträge
- `get*()` - Lädt Einträge
- `update*()` - Aktualisiert Einträge
- `delete*()` - Löscht Einträge
**Nutzt:** `*Access` für Zugriffskontrolle
**Beispiel:** `RealEstateChatObjects`
**Warum getrennt?**
- Separation of Concerns: Zugriffskontrolle ist separate Verantwortlichkeit
- Wiederverwendbarkeit: Access-Klasse kann von mehreren Interfaces genutzt werden
- Testbarkeit: Zugriffskontrolle kann unabhängig getestet werden
---
## Implementierung: Real Estate CRUD-Interface
Das Real Estate CRUD-Interface besteht aus **zwei separaten Dateien**, genau wie bei anderen Features (`interfaceDbAppObjects.py` + `interfaceDbAppAccess.py`).
### Datei 1: Access-Implementierung
**Datei:** `modules/interfaces/interfaceDbRealEstateAccess.py`
```python
"""
Access control for Real Estate interface.
Handles user access management and permission checks.
"""
import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelUam import User, UserPrivilege
logger = logging.getLogger(__name__)
class RealEstateAccess:
"""
Access control class for Real Estate interface.
Handles user access management and permission checks.
"""
def __init__(self, currentUser: User, db):
"""Initialize with user context."""
self.currentUser = currentUser
self.mandateId = currentUser.mandateId
self.userId = currentUser.id
if not self.mandateId or not self.userId:
raise ValueError("Invalid user context: mandateId and userId are required")
self.db = db
def uam(self, model_class: type, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Unified user access management function that filters data based on user privileges.
Args:
model_class: Pydantic model class for the table
recordset: Recordset to filter based on access rules
Returns:
Filtered recordset with access control attributes
"""
from modules.datamodels.datamodelUam import UserPrivilege
userPrivilege = self.currentUser.privilege
filtered_records = []
# System admins see all records
if userPrivilege == UserPrivilege.SYSADMIN:
filtered_records = recordset
# Admins see records in their mandate
elif userPrivilege == UserPrivilege.ADMIN:
filtered_records = [r for r in recordset if r.get("mandateId", "-") == self.mandateId]
# Regular users see only their records
else:
filtered_records = [
r for r in recordset
if r.get("mandateId", "-") == self.mandateId and r.get("_createdBy") == self.userId
]
# Add access control attributes
for record in filtered_records:
record["_hideView"] = False
record["_hideEdit"] = not self.canModify(model_class, record.get("id"))
record["_hideDelete"] = not self.canModify(model_class, record.get("id"))
return filtered_records
def canModify(self, model_class: type, recordId: Optional[str] = None) -> bool:
"""Checks if the current user can modify records."""
from modules.datamodels.datamodelUam import UserPrivilege
userPrivilege = self.currentUser.privilege
if userPrivilege == UserPrivilege.SYSADMIN:
return True
if recordId is not None:
records = self.db.getRecordset(model_class, recordFilter={"id": recordId})
if not records:
return False
record = records[0]
if userPrivilege == UserPrivilege.ADMIN and record.get("mandateId", "-") == self.mandateId:
return True
if (record.get("mandateId", "-") == self.mandateId and
record.get("_createdBy") == self.userId):
return True
return False
else:
return True # Regular users can create records
```
---
### Datei 2: Objects-Implementierung
**Datei:** `modules/interfaces/interfaceDbRealEstateObjects.py`
```python
"""
Interface to Real Estate database objects.
Uses PostgreSQL connector for data access with user/mandate filtering.
Handles CRUD operations on Real Estate entities (Projekt, Parzelle, etc.).
"""
import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelRealEstate import (
Projekt,
Parzelle,
Dokument,
Kanton,
Gemeinde,
Land,
GeoPolylinie,
GeoPunkt,
Kontext,
StatusProzess,
)
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
# Import Access-Klasse aus separater Datei
from modules.interfaces.interfaceDbRealEstateAccess import RealEstateAccess
logger = logging.getLogger(__name__)
# Singleton factory for Real Estate interfaces
_realEstateInterfaces = {}
class RealEstateObjects:
"""
Interface to Real Estate database objects.
Uses PostgreSQL connector for data access with user/mandate filtering.
Handles CRUD operations on Real Estate entities.
"""
def __init__(self, currentUser: Optional[User] = None):
"""Initializes the Real Estate Interface."""
self.currentUser = currentUser
self.userId = currentUser.id if currentUser else None
self.mandateId = currentUser.mandateId if currentUser else None
self.access = None
# Initialize database
self._initializeDatabase()
# Set user context if provided
if currentUser:
self.setUserContext(currentUser)
def _initializeDatabase(self):
"""Initialize PostgreSQL database connection."""
try:
# Get database configuration from environment
dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
dbUser = APP_CONFIG.get("DB_APP_USER")
dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
# Initialize database connector
self.db = DatabaseConnector(
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
dbPassword=dbPassword,
dbPort=dbPort,
userId=self.userId if self.userId else None,
)
logger.info(f"Real Estate database connector initialized for database: {dbDatabase}")
except Exception as e:
logger.error(f"Error initializing Real Estate database: {e}")
raise
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
self.currentUser = currentUser
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
if not self.userId or not self.mandateId:
raise ValueError("Invalid user context: id and mandateId are required")
# Initialize access control
self.access = RealEstateAccess(self.currentUser, self.db)
# Update database context
self.db.updateContext(self.userId)
# ===== Projekt Methods =====
def createProjekt(self, projekt: Projekt) -> Projekt:
"""Create a new project."""
# Ensure mandateId is set
if not projekt.mandateId:
projekt.mandateId = self.mandateId
# Apply access control
self.access.uam(Projekt, [])
# Save to database
self.db.recordCreate(Projekt, projekt.model_dump())
return projekt
def getProjekt(self, projektId: str) -> Optional[Projekt]:
"""Get a project by ID."""
records = self.db.getRecordset(
Projekt,
recordFilter={"id": projektId}
)
if not records:
return None
# Apply access control
filtered = self.access.uam(Projekt, records)
if not filtered:
return None
return Projekt(**filtered[0])
def updateProjekt(self, projektId: str, updateData: Dict[str, Any]) -> Optional[Projekt]:
"""Update a project."""
projekt = self.getProjekt(projektId)
if not projekt:
return None
# Check if user can modify
if not self.access.canModify(Projekt, projektId):
raise PermissionError(f"User {self.userId} cannot modify project {projektId}")
# Update fields
for key, value in updateData.items():
if hasattr(projekt, key):
setattr(projekt, key, value)
# Save to database
self.db.recordModify(Projekt, projektId, projekt.model_dump())
return projekt
def deleteProjekt(self, projektId: str) -> bool:
"""Delete a project."""
projekt = self.getProjekt(projektId)
if not projekt:
return False
# Check if user can modify
if not self.access.canModify(Projekt, projektId):
raise PermissionError(f"User {self.userId} cannot delete project {projektId}")
return self.db.recordDelete(Projekt, projektId)
# ===== Parzelle Methods =====
def createParzelle(self, parzelle: Parzelle) -> Parzelle:
"""Create a new plot."""
if not parzelle.mandateId:
parzelle.mandateId = self.mandateId
self.access.uam(Parzelle, [])
self.db.recordCreate(Parzelle, parzelle.model_dump())
return parzelle
def getParzelle(self, parzelleId: str) -> Optional[Parzelle]:
"""Get a plot by ID."""
records = self.db.getRecordset(
Parzelle,
recordFilter={"id": parzelleId}
)
if not records:
return None
filtered = self.access.uam(Parzelle, records)
if not filtered:
return None
return Parzelle(**filtered[0])
def updateParzelle(self, parzelleId: str, updateData: Dict[str, Any]) -> Optional[Parzelle]:
"""Update a plot."""
parzelle = self.getParzelle(parzelleId)
if not parzelle:
return None
if not self.access.canModify(Parzelle, parzelleId):
raise PermissionError(f"User {self.userId} cannot modify plot {parzelleId}")
for key, value in updateData.items():
if hasattr(parzelle, key):
setattr(parzelle, key, value)
self.db.recordModify(Parzelle, parzelleId, parzelle.model_dump())
return parzelle
def deleteParzelle(self, parzelleId: str) -> bool:
"""Delete a plot."""
parzelle = self.getParzelle(parzelleId)
if not parzelle:
return False
if not self.access.canModify(Parzelle, parzelleId):
raise PermissionError(f"User {self.userId} cannot delete plot {parzelleId}")
return self.db.recordDelete(Parzelle, parzelleId)
# ===== Dokument Methods =====
def createDokument(self, dokument: Dokument) -> Dokument:
"""Create a new document."""
if not dokument.mandateId:
dokument.mandateId = self.mandateId
self.access.uam(Dokument, [])
self.db.recordCreate(Dokument, dokument.model_dump())
return dokument
def getDokument(self, dokumentId: str) -> Optional[Dokument]:
"""Get a document by ID."""
records = self.db.getRecordset(
Dokument,
recordFilter={"id": dokumentId}
)
if not records:
return None
filtered = self.access.uam(Dokument, records)
if not filtered:
return None
return Dokument(**filtered[0])
# ... weitere CRUD-Methoden für andere Entitäten (Kanton, Gemeinde, Land, etc.)
def getInterface(currentUser: User) -> RealEstateObjects:
"""
Factory function to get or create a Real Estate interface instance for a user.
Uses singleton pattern per user.
"""
userKey = f"{currentUser.id}_{currentUser.mandateId}"
if userKey not in _realEstateInterfaces:
_realEstateInterfaces[userKey] = RealEstateObjects(currentUser)
return _realEstateInterfaces[userKey]
```
## Wichtige Punkte:
1. **DatabaseConnector**: Nutzt `connectorDbPostgre.DatabaseConnector` für Datenbankzugriff
2. **Access Control**: `RealEstateAccess` implementiert Benutzer- und Mandaten-Filterung
3. **Singleton Pattern**: `getInterface()` erstellt pro User eine Instanz
4. **CRUD-Operationen**: `recordCreate`, `recordModify`, `recordDelete`, `getRecordset` vom Connector
5. **MandateId**: Wird automatisch gesetzt, wenn nicht vorhanden
---
## Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)
### Wann benötigt?
**Kurze Antwort:** Nur wenn Sie **direkte SQL-Queries** ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.
#### Szenario 1: Alles über CRUD-Interface (EMPFOHLEN)
**Strukturiert und sicher:**
```python
# User schreibt: "Erstelle Projekt 'Test'"
# Feature-Logik nutzt CRUD-Interface:
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
mandateId=currentUser.mandateId, # Automatisch gesetzt
label="Test" # Validierung durch Pydantic
))
```
**Vorteile:**
-**Validierung**: Pydantic-Modelle prüfen alle Felder automatisch
-**Zugriffskontrolle**: `RealEstateAccess` prüft Berechtigungen
-**Sicherheit**: Kein SQL-Injection-Risiko
-**Geschäftslogik**: Automatisches Setzen von Systemfeldern (`_createdBy`, `_createdAt`)
-**Typsicherheit**: Fehler werden zur Entwicklungszeit erkannt
-**Wartbarkeit**: Zentrale CRUD-Methoden, einfach zu testen
#### Szenario 2: Direkte SQL-Queries (OPTIONAL)
**Nur für komplexe SELECT-Queries:**
```python
# Für komplexe Queries, die nicht über CRUD-Methoden abgedeckt sind
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
"SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)
```
**Warnung:**
- ⚠️ **Nur für SELECT-Queries**: Keine INSERT/UPDATE/DELETE über direkte SQL-Queries
- ⚠️ **Validierung erforderlich**: Queries sollten validiert werden
- ⚠️ **SQL-Injection-Risiko**: Immer Parameterisierung verwenden
#### Empfehlung
**Sie benötigen das Query-Interface, wenn Sie:**
-**Komplexe SELECT-Queries** benötigen (z.B. JOINs, Aggregationen)
-**Flexible Query-Ausführung** benötigen (nicht über CRUD-Methoden abgedeckt)
**Sie benötigen es NICHT, wenn Sie:**
-**Nur CRUD-Operationen** benötigen (verwenden Sie das CRUD-Interface)
-**Einfache Queries** haben (können über CRUD-Methoden abgedeckt werden)
**Für Production-Systeme:**
- **CRUD-Operationen**: Immer über CRUD-Interface
- **Komplexe Queries**: Optional über Query-Interface (nur SELECT)
### Struktur: Query-Interface (stateless)
**Eine Datei** für direkte SQL-Query-Ausführung:
#### Datei: `modules/interfaces/interfaceDbRealEstateChatObjects.py`
```python
"""
Interface for direct SQL query execution (stateless).
Uses PostgreSQL connector for direct query execution without session management.
"""
import logging
from typing import Dict, Any, Optional
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Singleton factory
_realEstateChatInterfaces = {}
class RealEstateChatObjects:
"""Interface for direct SQL query execution (stateless)."""
def __init__(self, currentUser: Optional[User] = None):
"""Initialize the Query Interface."""
self.currentUser = currentUser
self.userId = currentUser.id if currentUser else None
self.mandateId = currentUser.mandateId if currentUser else None
# Initialize database
self._initializeDatabase()
# Set user context if provided
if currentUser:
self.setUserContext(currentUser)
def _initializeDatabase(self):
"""Initialize PostgreSQL database connection."""
try:
dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
dbUser = APP_CONFIG.get("DB_APP_USER")
dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
self.db = DatabaseConnector(
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
dbPassword=dbPassword,
dbPort=dbPort,
userId=self.userId if self.userId else None,
)
logger.info(f"Real Estate Query database connector initialized for database: {dbDatabase}")
except Exception as e:
logger.error(f"Error initializing Real Estate Query database: {e}")
raise
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
self.currentUser = currentUser
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
if not self.userId or not self.mandateId:
raise ValueError("Invalid user context: id and mandateId are required")
# Update database context
self.db.updateContext(self.userId)
# ===== Database Query Execution =====
def executeQuery(self, queryText: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""
Execute a SQL query directly on the database (stateless).
WARNING: This method executes raw SQL. Ensure proper validation and sanitization
before calling this method. Consider implementing query whitelisting or
only allowing SELECT statements for production use.
Args:
queryText: SQL query string (preferably SELECT only)
parameters: Optional parameters for parameterized queries
Returns:
Dictionary with 'rows' (list of dicts), 'columns' (list of column names),
'rowCount' (int), and 'executionTime' (float)
"""
import time
try:
start_time = time.time()
# Ensure connection is alive
self.db._ensure_connection()
with self.db.connection.cursor() as cursor:
# Execute query
if parameters:
# Use parameterized query for safety
cursor.execute(queryText, parameters)
else:
cursor.execute(queryText)
# Fetch results
rows = cursor.fetchall()
# Convert to list of dictionaries
result_rows = [dict(row) for row in rows]
# Get column names
columns = [desc[0] for desc in cursor.description] if cursor.description else []
execution_time = time.time() - start_time
return {
"rows": result_rows,
"columns": columns,
"rowCount": len(result_rows),
"executionTime": execution_time,
}
except Exception as e:
logger.error(f"Error executing query: {e}")
raise
def getInterface(currentUser: User) -> RealEstateChatObjects:
"""
Factory function to get or create a Query interface instance for a user.
Uses singleton pattern per user.
"""
userKey = f"{currentUser.id}_{currentUser.mandateId}"
if userKey not in _realEstateChatInterfaces:
_realEstateChatInterfaces[userKey] = RealEstateChatObjects(currentUser)
return _realEstateChatInterfaces[userKey]
```
### Hinweise zur Implementierung
1. **Stateless**: Keine Session-Management-Funktionen
2. **Nur für Queries**: Primär für SELECT-Queries gedacht
3. **Sicherheit**: Immer Parameterisierung verwenden
4. **Validierung**: Queries sollten validiert werden (z.B. nur SELECT erlauben)
### Beispiel: Beide Interfaces zusammen nutzen
```python
from modules.interfaces.interfaceDbRealEstateChatObjects import getInterface as getChatInterface
from modules.interfaces.interfaceDbRealEstateObjects import getInterface as getRealEstateInterface
# CRUD-Interface für strukturierte Operationen
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
mandateId=currentUser.mandateId,
label="Neues Projekt"
))
# Query-Interface für komplexe SELECT-Queries (optional)
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
"SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)
```
---
## Zusammenfassung: Benötigte Dateien
### Erforderlich (für CRUD-Operationen):
1.`modules/datamodels/datamodelRealEstate.py`
- Real Estate-Datenmodelle (Projekt, Parzelle, Dokument, etc.)
2.`modules/interfaces/interfaceDbRealEstateAccess.py`
- Zugriffskontrolle für Real Estate-Entitäten (RealEstateAccess)
3.`modules/interfaces/interfaceDbRealEstateObjects.py`
- Real Estate CRUD-Interface (RealEstateObjects)
- Nutzt `interfaceDbRealEstateAccess.py`
- Haupt-Interface für alle CRUD-Operationen
### Optional (für direkte SQL-Queries):
4. ⚠️ `modules/interfaces/interfaceDbRealEstateChatObjects.py`
- Query-Interface für direkte SQL-Ausführung (RealEstateChatObjects)
- Stateless, keine Session-Management
- Nur wenn Sie komplexe SELECT-Queries benötigen
---
[← Zurück: Datenmodell erstellen](02-datamodels.md) | [Weiter: Feature-Logik implementieren →](04-feature-logic.md)