Modul 29 · Migration Deep Dive · Stufe 5 Enterprise Runtime

Service Registry und ServiceLoader

dynamische Service Registry wird nachvollziehbar in Java SPI mit ServiceLoader überführt. Die Akte zeigt nicht nur das Ziel, sondern die tatsächlichen Dateien, APIs, Dependencies, Codebelege, Tests, Risiken und Cutover-Schritte.

Service RegistryService Provider Interface
2 → 3Produktionsdateien
25 → 23Java-Zeilen
1 → 1Testdateien
0 / 0Dependencies entfernt / neu
NIEDRIGRisiko · Score 3

Was sich konkret ändert

DimensionLegacyModernMigrationskonsequenz
Programmiermodellimperativ / ohne Framework-Annotationenexplizite Java-AbstraktionenAnnotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen.
Abhängigkeiten1 direkte Dependencies1 direkte Dependencies0 neu, 0 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen.
Öffentliche API3 erkannte Methoden2 erkannte MethodenMethoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag.
Datenmodellclass ServiceRegistry, interface PaymentServiceclass DefaultPaymentProvider, class PaymentProviders, interface PaymentProviderFeldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet.
FehlerverhaltenLegacy-Exceptions und Rückgabewerteexplizitere Fach-/Framework-FehlerabbildungFehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden.
TestsBestands- und Golden-Master-TestsUnit-, Slice-, Contract- und IntegrationstestsDer Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt.
BetriebLegacy-Start/Lifecyclemodernes Packaging, Health und externe KonfigurationModule Path/JAR-Inhalt und Service Discovery im finalen Artefakt testen.
RollbackLegacy-Artefakt bleibt unverändertModern-Artefakt getrennt deploybarKein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein.

Fachlicher Vertrag: unverändert zu erhalten

  • PaymentProvider-SPI ist klein, stabil und implementation-neutral.
  • Provider-Auswahl bei mehreren Implementierungen ist deterministisch.
  • Dynamische OSGi-Lifecycle-Semantik wird nicht fälschlich als ServiceLoader-Eigenschaft angenommen.

Hauptrisiko und Testfokus

provider discovery, ordering and duplicates

no provider, multiple provider and packaging tests

Die Modernisierung gilt erst als abgeschlossen, wenn der fachliche Vertrag automatisiert belegt ist.

Verbindliches Code- und Rollenmapping

Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.

Legacy-Rolle / DateiModern-Rolle / DateiBedeutung
ServiceRegistry.java + PaymentService.javaPaymentProviders.java + PaymentProvider.javaRegistry wird Java-SPI-Resolver.
registrierte ImplementierungDefaultPaymentProvider.java + META-INF/servicesProvider wird über ServiceLoader paketiert.

Legacy-Quellinventar

DateiTypAnnotationenöffentliche APIPatternZeilen
PaymentService.javainterface PaymentService5
ServiceRegistry.javaclass ServiceRegistryvoid register(Class<T> type, T service)
void unregister(Class<?> type)
Optional<T> find(Class<T> type)
Service Registry20

Dependency- und Laufzeitmigration

StatusDependencyVersionScopePrüfung
BEIBEHALTENorg.junit.jupiter:junit-jupiterBOM/Parent → BOM/Parenttest → testGemeinsame Dependency; Version und Scope im effektiven POM prüfen.
Direkte POM-Daten ersetzen keinen erfolgreichen Online-Build mit transitiver SBOM- und CVE-Prüfung.

Vorher-/Nachher-Codebelege

Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.

Legacy
ServiceRegistry.java + PaymentService.java
Modern
PaymentProviders.java + PaymentProvider.java

Migrationsbedeutung: Registry wird Java-SPI-Resolver.

Legacy-Code

projects/29-osgi-service-registry/legacy/src/main/java/at/aydin/lab/osgi/legacy/ServiceRegistry.java

package at.aydin.lab.osgi.legacy;

import java.util.*;

// Design Pattern: Service Registry
// Zweck: Dienste können dynamisch registriert, ersetzt und entfernt werden.
public final class ServiceRegistry {
    private final Map<Class<?>, Object> services = new HashMap<>();
    public <T> void register(Class<T> type, T service) {
        services.put(type, service);
    }

    public void unregister(Class<?> type) {
        services.remove(type);
    }

    public <T> Optional<T> find(Class<T> type) {
        return Optional.ofNullable(type.cast(services.get(type)));
    }
}

Legacy-Code

projects/29-osgi-service-registry/legacy/src/main/java/at/aydin/lab/osgi/legacy/PaymentService.java

package at.aydin.lab.osgi.legacy;

public interface PaymentService {
    String pay(int cents);
}

Modern-Code

projects/29-osgi-service-registry/modern/src/main/java/at/aydin/lab/osgi/modern/PaymentProviders.java

package at.aydin.lab.osgi.modern;

import java.util.*;

public final class PaymentProviders {
    public PaymentProvider first() {
        return ServiceLoader.load(PaymentProvider.class).findFirst().orElseThrow();
    }
}

Modern-Code

projects/29-osgi-service-registry/modern/src/main/java/at/aydin/lab/osgi/modern/PaymentProvider.java

package at.aydin.lab.osgi.modern;

// Design Pattern: Service Provider Interface
// Zweck: Provider werden ohne zentrale Registry über Standard-Java-Metadaten entdeckt.
public interface PaymentProvider {
    String pay(int cents);
}
Legacy
registrierte Implementierung
Modern
DefaultPaymentProvider.java + META-INF/services

Migrationsbedeutung: Provider wird über ServiceLoader paketiert.

Legacy-Code

projects/29-osgi-service-registry/legacy/src/main/java/at/aydin/lab/osgi/legacy/ServiceRegistry.java

package at.aydin.lab.osgi.legacy;

import java.util.*;

// Design Pattern: Service Registry
// Zweck: Dienste können dynamisch registriert, ersetzt und entfernt werden.
public final class ServiceRegistry {
    private final Map<Class<?>, Object> services = new HashMap<>();
    public <T> void register(Class<T> type, T service) {
        services.put(type, service);
    }

    public void unregister(Class<?> type) {
        services.remove(type);
    }

    public <T> Optional<T> find(Class<T> type) {
        return Optional.ofNullable(type.cast(services.get(type)));
    }
}

Modern-Code

projects/29-osgi-service-registry/modern/src/main/java/at/aydin/lab/osgi/modern/DefaultPaymentProvider.java

package at.aydin.lab.osgi.modern;

public final class DefaultPaymentProvider implements PaymentProvider {
    public String pay(int cents) {
        return "PAID " + cents;
    }
}
Technischen Unified Diff öffnen
--- ServiceRegistry.java
+++ DefaultPaymentProvider.java
@@ -1,20 +1,7 @@
-package at.aydin.lab.osgi.legacy;
+package at.aydin.lab.osgi.modern;
 
-import java.util.*;
-
-// Design Pattern: Service Registry
-// Zweck: Dienste können dynamisch registriert, ersetzt und entfernt werden.
-public final class ServiceRegistry {
-    private final Map<Class<?>, Object> services = new HashMap<>();
-    public <T> void register(Class<T> type, T service) {
-        services.put(type, service);
-    }
-
-    public void unregister(Class<?> type) {
-        services.remove(type);
-    }
-
-    public <T> Optional<T> find(Class<T> type) {
-        return Optional.ofNullable(type.cast(services.get(type)));
+public final class DefaultPaymentProvider implements PaymentProvider {
+    public String pay(int cents) {
+        return "PAID " + cents;
     }
 }

Umsetzungsplan mit Qualitäts-Gates

  1. Arbeitspaket 1
    Benötigte Registry-Funktionen und echte Dynamik-Anforderungen klären. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  2. Arbeitspaket 2
    PaymentProvider als Java-SPI definieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  3. Arbeitspaket 3
    META-INF/services korrekt paketieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  4. Arbeitspaket 4
    PaymentProviders mit Auswahl-/Fehlerregeln implementieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  5. Arbeitspaket 5
    No-/Multi-provider- und Packaging-Tests ausführen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  6. Arbeitspaket 6
    Legacy Registry nur entfernen, wenn keine dynamische Nachinstallation benötigt wird. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.

Konkreter Test- und Abnahmekatalog

IDEbenePrüfungerforderlicher Nachweis
29-OSGI-SERVICE-REGISTRY-A01Integration/ContractProvider wird im gebauten JAR gefunden.Automatisierter Test und CI-Protokoll
29-OSGI-SERVICE-REGISTRY-A02Integration/ContractKein Provider liefert verständlichen Fehler.Automatisierter Test und CI-Protokoll
29-OSGI-SERVICE-REGISTRY-A03Integration/ContractMehrere Provider folgen dokumentierter Priorität.Automatisierter Test und CI-Protokoll
29-OSGI-SERVICE-REGISTRY-A04Integration/ContractSPI enthält keine Implementierungsdetails.Automatisierter Test und CI-Protokoll
29-OSGI-SERVICE-REGISTRY-A05Integration/ContractGrenze zu echtem OSGi ist in Architektur dokumentiert.Automatisierter Test und CI-Protokoll
29-OSGI-SERVICE-REGISTRY-T01UnitFachlogik ohne Container oder externen Dienst testen.Unit-Test
29-OSGI-SERVICE-REGISTRY-T02RegressionLegacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen.Vergleichsreport
29-OSGI-SERVICE-REGISTRY-T03NegativeFehlerhafte, leere und grenzwertige Eingaben prüfen.Negativtest
29-OSGI-SERVICE-REGISTRY-T04OperationsStart, Health, Shutdown und Konfigurationsfehler prüfen.Deployment-/Startprotokoll
29-OSGI-SERVICE-REGISTRY-T05PackagingModule Path, JAR-Inhalt und Service Discovery aus finalem Artefakt prüfen.Packaging-Smoke-Test
29-OSGI-SERVICE-REGISTRY-F01Fokusno provider, multiple provider and packaging testsModulspezifischer Testreport

Risikoregister des Moduls

RisikoAuswirkungGegenmaßnahmeGate
provider discovery, ordering and duplicatesNIEDRIGno provider, multiple provider and packaging testsvor Cutover

Rollback und Koexistenz

ServiceRegistry kann als alternativer ProviderResolver hinter derselben SPI bestehen bleiben, falls dynamische Registrierung noch benötigt wird.

Abbruchkriterien

  • Fachlicher Golden-Master weicht ab.
  • Daten-, Nachrichten- oder API-Kompatibilität ist ungeklärt.
  • Fehlerquote, Latenz oder Ressourcenverbrauch überschreiten das vereinbarte Limit.
  • Monitoring oder Rückfallpfad ist nicht funktionsfähig.

Definition of Done

Direkte Arbeitslinks

⌂ Cockpit