Files
motia-iii/REFACTORING_SUMMARY.md
bsiggel 69a48f5f9a Implement central configuration, custom exceptions, logging utilities, Pydantic models, and Redis client for BitByLaw integration
- 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.
2026-03-03 17:18:49 +00:00

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 Detection
  • API_CONFIG - Timeouts, Rate Limiting
  • ADVOWARE_CONFIG - Token, Auth, Read-only Fields
  • ESPOCRM_CONFIG - Pagination, Notifications
  • FEATURE_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:

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

  1. 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
  1. 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)
  1. 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
  1. 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

  1. Unit Tests schreiben (min. 60% Coverage)

    • Exception Handling Tests
    • Mapper Tests mit Pydantic
    • Redis Factory Tests
  2. Batch Operations implementieren

    • Parallele API-Calls
    • Bulk Updates
  3. Monitoring verbessern

    • Performance Metrics aus Logger nutzen
    • Redis Health Checks
  4. Dokumentation erweitern

    • API-Docs generieren (Sphinx)
    • Error Handling Guide

Breakfree Changes

⚠️ Minimale Breaking Changes:

  1. Import-Pfade haben sich geändert:

    • AdvowareTokenErrorAdvowareAuthError
    • EspoCRMErrorEspoCRMAPIError
  2. Redis wird jetzt über Factory bezogen:

    • Statt direktem redis.Redis()get_redis_client()

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: