- Added `config.py` for centralized configuration management including Sync, API, Advoware, EspoCRM, Redis, Logging, Calendar Sync, and Feature Flags. - Created `exceptions.py` with a hierarchy of custom exceptions for integration errors, API errors, sync errors, and Redis errors. - Developed `logging_utils.py` for a unified logging wrapper supporting structured logging and performance tracking. - Defined Pydantic models in `models.py` for data validation of Advoware and EspoCRM entities, including sync operation models. - Introduced `redis_client.py` for a centralized Redis client factory with connection pooling, automatic reconnection, and health checks.
8.6 KiB
Code Refactoring - Verbesserungen Übersicht
Datum: 3. März 2026
Zusammenfassung
Umfassendes Refactoring zur Verbesserung von Robustheit, Eleganz und Effizienz des BitByLaw Integration Codes.
Implementierte Verbesserungen
1. ✅ Custom Exception Classes (services/exceptions.py)
Problem: Zu generisches Exception Handling mit except Exception
Lösung: Hierarchische Exception-Struktur:
from services.exceptions import (
AdvowareAPIError,
AdvowareAuthError,
AdvowareTimeoutError,
EspoCRMAPIError,
EspoCRMAuthError,
RetryableError,
NonRetryableError,
LockAcquisitionError,
ValidationError
)
# Verwendung:
try:
result = await advoware.api_call(...)
except AdvowareTimeoutError:
# Spezifisch für Timeouts
raise RetryableError()
except AdvowareAuthError:
# Auth-Fehler nicht retryable
raise
except AdvowareAPIError as e:
# Andere API-Fehler
if is_retryable(e):
# Retry logic
Vorteile:
- Präzise Fehlerbehandlung
- Besseres Error Tracking
- Automatische Retry-Klassifizierung mit
is_retryable()
2. ✅ Redis Client Factory (services/redis_client.py)
Problem: Duplizierte Redis-Initialisierung in 4+ Dateien
Lösung: Zentralisierte Redis Client Factory mit Singleton Pattern:
from services.redis_client import get_redis_client, is_redis_available
# Strict mode: Exception bei Fehler
redis_client = get_redis_client(strict=True)
# Optional mode: None bei Fehler (für optionale Features)
redis_client = get_redis_client(strict=False)
# Health Check
if is_redis_available():
# Redis verfügbar
Vorteile:
- DRY (Don't Repeat Yourself)
- Connection Pooling
- Zentrale Konfiguration
- Health Checks
3. ✅ Pydantic Models für Validation (services/models.py)
Problem: Keine Datenvalidierung, unsichere Typen
Lösung: Pydantic Models mit automatischer Validierung:
from services.models import (
AdvowareBeteiligteCreate,
EspoCRMBeteiligteCreate,
validate_beteiligte_advoware
)
# Automatische Validierung:
try:
validated = AdvowareBeteiligteCreate.model_validate(data)
except ValidationError as e:
# Handle validation errors
# Helper:
validated = validate_beteiligte_advoware(data)
Features:
- Type Safety
- Automatische Validierung (Geburtsdatum, Name, etc.)
- Enums für Status/Rechtsformen
- Field Validators
4. ✅ Zentrale Konfiguration (services/config.py)
Problem: Magic Numbers und Strings überall im Code
Lösung: Zentrale Config mit Dataclasses:
from services.config import (
SYNC_CONFIG,
API_CONFIG,
ADVOWARE_CONFIG,
ESPOCRM_CONFIG,
FEATURE_FLAGS,
get_retry_delay_seconds,
get_lock_key
)
# Verwendung:
max_retries = SYNC_CONFIG.max_retries # 5
lock_ttl = SYNC_CONFIG.lock_ttl_seconds # 900
backoff = SYNC_CONFIG.retry_backoff_minutes # [1, 5, 15, 60, 240]
# Helper Functions:
lock_key = get_lock_key('cbeteiligte', entity_id)
retry_delay = get_retry_delay_seconds(attempt=2) # 15 * 60 seconds
Konfigurationsbereiche:
SYNC_CONFIG- Retry, Locking, Change DetectionAPI_CONFIG- Timeouts, Rate LimitingADVOWARE_CONFIG- Token, Auth, Read-only FieldsESPOCRM_CONFIG- Pagination, NotificationsFEATURE_FLAGS- Feature Toggles
5. ✅ Konsistentes Logging (services/logging_utils.py)
Problem: Inkonsistentes Logging (3 verschiedene Patterns)
Lösung: Unified Logger mit Context-Support:
from services.logging_utils import get_logger, get_service_logger
# Service Logger:
logger = get_service_logger('advoware', context)
logger.info("Message", entity_id="123")
# Mit Context Manager für Timing:
with logger.operation('sync_entity', entity_id='123'):
# Do work
pass # Automatisches Timing und Error Logging
# API Call Tracking:
with logger.api_call('/api/v1/Beteiligte', method='POST'):
result = await api.post(...)
Features:
- Motia FlowContext Support
- Structured Logging
- Automatisches Performance Tracking
- Context Fields
6. ✅ Spezifische Exceptions in Services
Aktualisierte Services:
- advoware.py - AdvowareAPIError, AdvowareAuthError, AdvowareTimeoutError
- espocrm.py - EspoCRMAPIError, EspoCRMAuthError, EspoCRMTimeoutError
- sync_utils_base.py - LockAcquisitionError
- beteiligte_sync_utils.py - SyncError
Beispiel:
# Vorher:
except Exception as e:
logger.error(f"Error: {e}")
# Nachher:
except AdvowareTimeoutError:
raise RetryableError("Request timed out")
except AdvowareAuthError:
raise # Nicht retryable
except AdvowareAPIError as e:
if is_retryable(e):
# Retry
7. ✅ Type Hints ergänzt
Verbesserte Type Hints in:
- Service-Methoden (advoware.py, espocrm.py)
- Mapper-Funktionen (espocrm_mapper.py)
- Utility-Klassen (sync_utils_base.py, beteiligte_sync_utils.py)
- Step Handler
Beispiel:
# Vorher:
async def handler(event_data, ctx):
...
# Nachher:
async def handler(
event_data: Dict[str, Any],
ctx: FlowContext[Any]
) -> Optional[Dict[str, Any]]:
...
Migration Guide
Für bestehenden Code
- Exception Handling aktualisieren:
# Alt:
try:
result = await api.call()
except Exception as e:
logger.error(f"Error: {e}")
# Neu:
try:
result = await api.call()
except AdvowareTimeoutError:
# Spezifisch behandeln
raise RetryableError()
except AdvowareAPIError as e:
logger.error(f"API Error: {e}")
if is_retryable(e):
# Retry
- Redis initialisieren:
# Alt:
redis_client = redis.Redis(host=..., port=...)
# Neu:
from services.redis_client import get_redis_client
redis_client = get_redis_client(strict=False)
- Konstanten verwenden:
# Alt:
MAX_RETRIES = 5
LOCK_TTL = 900
# Neu:
from services.config import SYNC_CONFIG
max_retries = SYNC_CONFIG.max_retries
lock_ttl = SYNC_CONFIG.lock_ttl_seconds
- Logging standardisieren:
# Alt:
logger = logging.getLogger(__name__)
logger.info("Message")
# Neu:
from services.logging_utils import get_service_logger
logger = get_service_logger('my_service', context)
logger.info("Message", entity_id="123")
Performance-Verbesserungen
- ✅ Redis Connection Pooling (max 50 Connections)
- ✅ Token Caching optimiert
- ✅ Bessere Error Classification (weniger unnötige Retries)
- ⚠️ Noch TODO: Batch Operations für parallele Syncs
Feature Flags
Neue Features können über FEATURE_FLAGS gesteuert werden:
from services.config import FEATURE_FLAGS
# Aktivieren/Deaktivieren:
FEATURE_FLAGS.strict_validation = True # Pydantic Validation
FEATURE_FLAGS.kommunikation_sync_enabled = False # Noch in Entwicklung
FEATURE_FLAGS.parallel_sync_enabled = False # Experimentell
Testing
Unit Tests sollten nun leichter sein:
# Mock Redis:
from services.redis_client import RedisClientFactory
RedisClientFactory._instance = mock_redis
# Mock Exceptions:
from services.exceptions import AdvowareAPIError
raise AdvowareAPIError("Test error", status_code=500)
# Validate Models:
from services.models import validate_beteiligte_advoware
with pytest.raises(ValidationError):
validate_beteiligte_advoware(invalid_data)
Nächste Schritte
-
Unit Tests schreiben (min. 60% Coverage)
- Exception Handling Tests
- Mapper Tests mit Pydantic
- Redis Factory Tests
-
Batch Operations implementieren
- Parallele API-Calls
- Bulk Updates
-
Monitoring verbessern
- Performance Metrics aus Logger nutzen
- Redis Health Checks
-
Dokumentation erweitern
- API-Docs generieren (Sphinx)
- Error Handling Guide
Breakfree Changes
⚠️ Minimale Breaking Changes:
-
Import-Pfade haben sich geändert:
AdvowareTokenError→AdvowareAuthErrorEspoCRMError→EspoCRMAPIError
-
Redis wird jetzt über Factory bezogen:
- Statt direktem
redis.Redis()→get_redis_client()
- Statt direktem
Migration ist einfach: Imports aktualisieren, Code läuft sonst identisch.
Autoren
- Code Refactoring: GitHub Copilot
- Review: BitByLaw Team
- Datum: 3. März 2026
Fragen?
Bei Fragen zum Refactoring siehe:
- services/README.md - Service-Layer Dokumentation
- exceptions.py - Exception Hierarchie
- config.py - Alle Konfigurationsoptionen