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.
This commit is contained in:
382
REFACTORING_SUMMARY.md
Normal file
382
REFACTORING_SUMMARY.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# 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](services/exceptions.py))
|
||||
|
||||
**Problem:** Zu generisches Exception Handling mit `except Exception`
|
||||
|
||||
**Lösung:** Hierarchische Exception-Struktur:
|
||||
|
||||
```python
|
||||
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](services/redis_client.py))
|
||||
|
||||
**Problem:** Duplizierte Redis-Initialisierung in 4+ Dateien
|
||||
|
||||
**Lösung:** Zentralisierte Redis Client Factory mit Singleton Pattern:
|
||||
|
||||
```python
|
||||
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](services/models.py))
|
||||
|
||||
**Problem:** Keine Datenvalidierung, unsichere Typen
|
||||
|
||||
**Lösung:** Pydantic Models mit automatischer Validierung:
|
||||
|
||||
```python
|
||||
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](services/config.py))
|
||||
|
||||
**Problem:** Magic Numbers und Strings überall im Code
|
||||
|
||||
**Lösung:** Zentrale Config mit Dataclasses:
|
||||
|
||||
```python
|
||||
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](services/logging_utils.py))
|
||||
|
||||
**Problem:** Inkonsistentes Logging (3 verschiedene Patterns)
|
||||
|
||||
**Lösung:** Unified Logger mit Context-Support:
|
||||
|
||||
```python
|
||||
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](services/advoware.py) - AdvowareAPIError, AdvowareAuthError, AdvowareTimeoutError
|
||||
- [espocrm.py](services/espocrm.py) - EspoCRMAPIError, EspoCRMAuthError, EspoCRMTimeoutError
|
||||
- [sync_utils_base.py](services/sync_utils_base.py) - LockAcquisitionError
|
||||
- [beteiligte_sync_utils.py](services/beteiligte_sync_utils.py) - SyncError
|
||||
|
||||
**Beispiel:**
|
||||
```python
|
||||
# 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:**
|
||||
```python
|
||||
# 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:**
|
||||
```python
|
||||
# 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
|
||||
```
|
||||
|
||||
2. **Redis initialisieren:**
|
||||
```python
|
||||
# Alt:
|
||||
redis_client = redis.Redis(host=..., port=...)
|
||||
|
||||
# Neu:
|
||||
from services.redis_client import get_redis_client
|
||||
redis_client = get_redis_client(strict=False)
|
||||
```
|
||||
|
||||
3. **Konstanten verwenden:**
|
||||
```python
|
||||
# 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
|
||||
```
|
||||
|
||||
4. **Logging standardisieren:**
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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:**
|
||||
|
||||
```python
|
||||
# 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:
|
||||
- `AdvowareTokenError` → `AdvowareAuthError`
|
||||
- `EspoCRMError` → `EspoCRMAPIError`
|
||||
|
||||
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:
|
||||
- [services/README.md](services/README.md) - Service-Layer Dokumentation
|
||||
- [exceptions.py](services/exceptions.py) - Exception Hierarchie
|
||||
- [config.py](services/config.py) - Alle Konfigurationsoptionen
|
||||
Reference in New Issue
Block a user