12 KiB
Discovery Registry Loading - Problem-Analyse und Refactoring-Vorschläge
Problem-Identifikation
Aktuelles Problem
Die DiscoveryRegistry wird über zwei verschiedene Mechanismen geladen, was zu Inkonsistenzen führt:
-
DiscoveryServiceBootstrapper::bootstrap()(Runtime)- Wird in
ContainerBootstrapper::autowire()aufgerufen - Verwendet
DiscoveryCacheManagermitisStale()Prüfung - Registriert
DiscoveryRegistryals Singleton im Container - Führt Discovery durch, wenn Cache stale ist oder nicht existiert
- Wird in
-
DiscoveryRegistryInitializer(Build-Time)- Ist ein
#[Initializer(ContextType::ALL)] - Lädt aus
storage/discovery/Dateien (Build-Time Storage) - Wird möglicherweise vor/nach
DiscoveryServiceBootstrapperausgeführt - Kann eine alte Registry laden, die dann als Singleton registriert wird
- Ist ein
Konflikt-Szenario
Szenario 1: DiscoveryRegistryInitializer läuft zuerst
1. DiscoveryRegistryInitializer läuft (Initializer-System)
2. Lädt alte Registry aus storage/discovery/ (ohne DatabaseAssetGalleryDataProvider)
3. Registriert als Singleton im Container
4. DiscoveryServiceBootstrapper läuft
5. Prüft Cache -> findet alte Registry (nicht stale, weil Datei vor Cache-Erstellung geändert wurde)
6. Verwendet alte Registry -> Provider fehlt
Szenario 2: DiscoveryServiceBootstrapper läuft zuerst
1. DiscoveryServiceBootstrapper läuft (autowire)
2. Führt Discovery durch -> findet DatabaseAssetGalleryDataProvider
3. Registriert als Singleton
4. DiscoveryRegistryInitializer läuft später
5. Lädt alte Registry aus storage/discovery/
6. Überschreibt Singleton? -> Provider geht verloren
Root Cause Analysis
Problem 1: Zwei verschiedene Cache-Mechanismen
- Runtime-Cache:
DiscoveryCacheManagermitisStale()Prüfung - Build-Time Storage:
storage/discovery/Dateien (veraltet?)
Problem 2: Unklare Ausführungsreihenfolge
DiscoveryServiceBootstrapperläuft inautowire()(explizit)DiscoveryRegistryInitializerläuft über Initializer-System (automatisch)- Keine garantierte Reihenfolge
Problem 3: isStale() Prüfung funktioniert nicht korrekt
- BEHOBEN: Verwendet jetzt Cache-Erstellungszeit statt Request-Zeit
- Aber: Wenn
DiscoveryRegistryInitializereine alte Registry lädt, wird diese nicht als stale erkannt
Refactoring-Vorschläge
Option 1: DiscoveryRegistryInitializer entfernen (Empfohlen)
Vorteile:
- Eliminiert Konflikt zwischen zwei Mechanismen
- Einheitliche Cache-Strategie über
DiscoveryCacheManager isStale()Prüfung funktioniert korrekt
Nachteile:
- Build-Time Storage wird nicht mehr verwendet
- Möglicherweise Performance-Verlust beim ersten Request
Implementierung:
DiscoveryRegistryInitializerentfernen oder deaktivieren- Sicherstellen, dass
DiscoveryServiceBootstrapperimmer läuft DiscoveryCacheManagerals einzige Quelle für Discovery-Registry
Option 2: DiscoveryRegistryInitializer als Fallback
Vorteile:
- Behält Build-Time Storage für Performance
- Fallback wenn Runtime-Cache nicht verfügbar
Nachteile:
- Komplexere Logik
- Mögliche Inkonsistenzen zwischen Build-Time und Runtime
Implementierung:
DiscoveryServiceBootstrapperprüft zuerst Runtime-Cache- Wenn nicht verfügbar/stale, prüft
DiscoveryRegistryInitializer - Nur wenn beide fehlschlagen, führt Discovery durch
Option 3: Einheitliche Cache-Strategie
Vorteile:
- Einheitliche Quelle für Discovery-Registry
- Klare Verantwortlichkeiten
Nachteile:
- Größere Refactoring-Arbeit
Implementierung:
DiscoveryCacheManagerverwendetstorage/discovery/als BackendDiscoveryRegistryInitializerentfernenDiscoveryServiceBootstrapperverwendet nurDiscoveryCacheManager
Option 4: Priority-basierte Ausführungsreihenfolge
Vorteile:
- Beide Mechanismen bleiben erhalten
- Klare Ausführungsreihenfolge
Nachteile:
- Komplexere Initializer-Logik
- Mögliche Race-Conditions
Implementierung:
DiscoveryServiceBootstrappermit höherer PriorityDiscoveryRegistryInitializerprüft ob Registry bereits existiert- Nur laden wenn nicht vorhanden
Empfohlene Lösung: Option 1 + Verbesserungen
Schritt 1: DiscoveryRegistryInitializer deaktivieren/entfernen
// DiscoveryRegistryInitializer.php
#[Initializer(ContextType::ALL)]
public function __invoke(): DiscoveryRegistry
{
// DEPRECATED: This initializer is deprecated in favor of DiscoveryServiceBootstrapper
// Check if DiscoveryRegistry already exists (from DiscoveryServiceBootstrapper)
if ($this->container->has(DiscoveryRegistry::class)) {
return $this->container->get(DiscoveryRegistry::class);
}
// Fallback: Load from storage (for backward compatibility)
$attributes = $this->discoveryLoader->loadAttributes() ?? new AttributeRegistry();
$interfaces = $this->discoveryLoader->loadInterfaces() ?? new InterfaceRegistry();
$templates = $this->discoveryLoader->loadTemplates() ?? new TemplateRegistry();
return new DiscoveryRegistry(
attributes: $attributes,
interfaces: $interfaces,
templates: $templates
);
}
Schritt 2: DiscoveryServiceBootstrapper als einzige Quelle
// DiscoveryServiceBootstrapper.php
public function bootstrap(): DiscoveryRegistry
{
// Prüfe ob bereits registriert (z.B. durch DiscoveryRegistryInitializer)
if ($this->container->has(DiscoveryRegistry::class)) {
$existing = $this->container->get(DiscoveryRegistry::class);
// Prüfe ob stale
if ($this->isRegistryStale($existing)) {
// Force refresh
$this->container->forget(DiscoveryRegistry::class);
} else {
return $existing;
}
}
// ... rest of bootstrap logic
}
Schritt 3: Verbesserte isStale() Prüfung
// DiscoveryCacheManager.php
private function isStale(DiscoveryContext $context, DiscoveryRegistry $registry, ?\DateTimeInterface $cacheStartTime = null): bool
{
// ... existing logic
// ZUSÄTZLICH: Prüfe ob Dateien seit Cache-Erstellung geändert wurden
// Dies ist bereits implementiert, aber sollte auch für Build-Time Storage gelten
}
Weitere Verbesserungen
1. Discovery-Registry Versionierung
- Jede Registry hat eine Version/Timestamp
- Vergleich zwischen Build-Time und Runtime-Cache
- Automatische Invalidation bei Versionsunterschieden
2. Unified Discovery Storage
- Einheitliche Storage-Strategie für Build-Time und Runtime
DiscoveryCacheManagerverwendetstorage/discovery/als Backend- Konsistente Cache-Invalidation
3. Discovery-Registry Factory
- Zentrale Factory für Discovery-Registry-Erstellung
- Einheitliche Logik für alle Quellen
- Klare Verantwortlichkeiten
4. Logging und Monitoring
- Besseres Logging für Discovery-Registry-Loading
- Metriken für Cache-Hits/Misses
- Warnungen bei Inkonsistenzen
Migration-Plan
- Phase 1:
DiscoveryRegistryInitializerals Fallback behalten, aber prüfen ob Registry bereits existiert - Phase 2:
DiscoveryServiceBootstrapperprüft ob Registry stale ist, auch wenn bereits registriert - Phase 3:
DiscoveryRegistryInitializervollständig entfernen - Phase 4: Build-Time Storage optional machen (nur für Performance-Optimierung)
Testing-Strategie
- Unit-Tests für
DiscoveryCacheManager::isStale() - Integration-Tests für Discovery-Registry-Loading
- E2E-Tests für Provider-Resolution
- Performance-Tests für Cache-Hit-Rate
Implementierte Änderungen
1. isStale() Prüfung korrigiert ✅
- Datei:
src/Framework/Discovery/Storage/DiscoveryCacheManager.php - Änderung:
isStale()verwendet jetzt Cache-Erstellungszeit statt Request-Zeit - Implementierung:
startTimewird beim Speichern im Cache gespeichert und beim Laden verwendet
2. DiscoveryRegistryInitializer entfernt ✅
- Datei:
src/Framework/Discovery/DiscoveryRegistryInitializer.php(entfernt) - Änderung: Initializer wurde vollständig entfernt, da er nur noch als No-Op fungierte
- Implementierung:
DiscoveryServiceBootstrapperist jetzt die einzige Quelle für Discovery-Registry
3. DiscoveryServiceBootstrapper verbessert ✅
- Datei:
src/Framework/Discovery/DiscoveryServiceBootstrapper.php - Änderung: Prüft ob existierende Registry stale ist, bevor Discovery durchgeführt wird
- Implementierung:
isRegistryStale()Methode hinzugefügt
4. Cache-Struktur Migration ✅
- Datei:
src/Framework/Discovery/Storage/DiscoveryCacheManager.php - Änderung: Automatische Migration von altem Cache-Format zu neuem Format mit
startTimeundversion - Implementierung:
upgradeCacheEntry()undvalidateCacheStructure()Methoden hinzugefügt
5. Registry Versionierung ✅
- Datei:
src/Framework/Discovery/Storage/DiscoveryCacheManager.php - Änderung: Cache-Struktur erweitert um
versionFeld für Registry-Versionierung - Implementierung:
getRegistryVersion()Methode erstellt Version basierend auf Registry-Content-Hash
6. Unified Discovery Storage ✅
- Datei:
src/Framework/Discovery/Storage/DiscoveryCacheManager.php - Änderung: Build-Time Storage als optionales Backend hinzugefügt
- Implementierung:
tryGetFromBuildTimeStorage()Methode prüft Build-Time Storage vor Runtime-Cache
7. Verbessertes Logging und Monitoring ✅
- Dateien:
src/Framework/Discovery/Storage/DiscoveryCacheManager.phpsrc/Framework/Discovery/Results/DiscoveryRegistry.php
- Änderung: Strukturiertes Logging, Metriken-Sammlung, Debug-Helpers hinzugefügt
- Implementierung:
getMetadata()Methode inDiscoveryRegistrygetSource()Methode inDiscoveryRegistry- Erweiterte Logging-Statements mit Source-Informationen
Weitere Refactoring-Vorschläge
Refactoring 1: Einheitliche Cache-Struktur
Problem: Cache-Struktur wurde geändert (Array mit registry und startTime), aber alte Caches haben noch das alte Format.
Lösung:
- Migration-Script für alte Cache-Einträge
- Oder: Automatische Erkennung und Konvertierung beim Laden
Refactoring 2: DiscoveryRegistryInitializer entfernen
Problem: Zwei Mechanismen für Discovery-Registry-Loading führen zu Konflikten.
Lösung:
DiscoveryRegistryInitializervollständig entfernen- Oder: Nur als Fallback verwenden (bereits implementiert)
DiscoveryServiceBootstrapperals einzige Quelle
Refactoring 3: Build-Time Storage optional machen
Problem: Build-Time Storage (storage/discovery/) wird möglicherweise nicht mehr verwendet.
Lösung:
- Build-Time Storage als Performance-Optimierung optional machen
- Nur verwenden wenn verfügbar und aktuell
- Runtime-Cache als primäre Quelle
Refactoring 4: Discovery-Registry Versionierung
Problem: Keine Möglichkeit, verschiedene Versionen der Registry zu vergleichen.
Lösung:
- Jede Registry hat eine Version/Timestamp
- Vergleich zwischen Build-Time und Runtime-Cache
- Automatische Invalidation bei Versionsunterschieden
Refactoring 5: Unified Discovery Storage
Problem: Zwei verschiedene Storage-Mechanismen (Build-Time und Runtime).
Lösung:
- Einheitliche Storage-Strategie
DiscoveryCacheManagerverwendetstorage/discovery/als Backend- Konsistente Cache-Invalidation
Refactoring 6: Discovery-Registry Factory
Problem: Discovery-Registry wird an mehreren Stellen erstellt.
Lösung:
- Zentrale Factory für Discovery-Registry-Erstellung
- Einheitliche Logik für alle Quellen
- Klare Verantwortlichkeiten
Refactoring 7: Verbessertes Logging
Problem: Schwierig zu debuggen, welche Registry verwendet wird.
Lösung:
- Besseres Logging für Discovery-Registry-Loading
- Metriken für Cache-Hits/Misses
- Warnungen bei Inkonsistenzen