CodeGym /Kurse /Spring Boot /optional: und die Startrichtlinie

optional: und die Startrichtlinie

Spring Boot
Level 16, Lektion 2
Verfügbar

1. Fehlende Datei als Richtlinie

Wenn man mit externer Konfiguration arbeitet, gerät man sehr schnell in den Modus „na ja, manchmal sind Dateien nicht da – und das ist auch okay“. Und dann stellt sich plötzlich heraus, dass die Anwendung in einem „halb-funktionsfähigen“ Zustand gestartet ist: Port falsch, Flags falsch, keine Daten, und in den Logs herrscht Stille. In Spring Boot ist das Fehlen einer Konfigurationsressource ein Signal, anhand dessen Sie eine Strategie wählen: strikt abbrechen beim Start (fail-fast) oder einen sanften Start erlauben (tolerant startup).

Stellen Sie sich vor, die Konfiguration sei eine Anleitung zum Zusammenbau von Möbeln. Wenn Sie die Seite verlieren, auf der steht, wie man die Beine anschraubt, dann bedeutet „sanfter Start“: Sie bauen den Schrank ohne Beine zusammen und sagen fröhlich: „Fertig!“. Sie werden wohl kaum zufrieden sein, wenn Sie später versuchen, Tassen daraufzustellen. Andererseits: Wenn Sie einen „dekorativen Aufkleber 3×3 mm“ verlieren, ist es auch seltsam, deswegen mit einem Fehler abzustürzen. Genau das ist die Wahl der Richtlinie.

In Spring Boot 4 ist die Idee einfach: Wenn Sie explizit sagen „binde diese Datei / dieses Verzeichnis ein“, dann betrachtet Boot dies standardmäßig als zwingende Voraussetzung. Wenn die Ressource nicht gefunden wird, ist das fast immer ein Fehler und der Start muss abgebrochen werden. Aber wir haben einen Mechanismus, der uns erlaubt zu sagen: „Diese Einbindung ist optional; wenn du sie nicht findest – überspringe sie einfach“. Dieser Mechanismus heißt optional:.

2. Standardverhalten

Kommen wir nun etwas näher zur Praxis. Es gibt zwei Arten von Situationen, in denen „Datei nicht gefunden“ auftritt.

Die erste: Sie verwenden spring.config.import und geben einen konkreten Ort an: classpath:... oder file:.... Wenn Boot diese Ressource nicht finden kann, bricht er normalerweise den Start ab. Das ist logisch: Sie haben selbst eine explizite Abhängigkeit der Konfiguration von der Datei hinzugefügt.

Die zweite: Sie steuern die Konfigurationssuche über spring.config.location oder erweitern sie über spring.config.additional-location. Dort gilt die gleiche Logik: Wenn Sie gesagt haben „suche Konfiguration auch hier“ und dort ist nichts, könnte das für Boot ein Zeichen sein, dass Sie den Pfad falsch angegeben oder vergessen haben, Dateien abzulegen. Und wieder: Standardmäßig ist es besser, anzuhalten und den Benutzer die Situation korrigieren zu lassen, als den Dienst „irgendwie“ zu starten.

Es ist wichtig, die Philosophie zu verstehen: Spring Boot bemüht sich, vorhersagbar zu sein. Er wählt lieber „gar nicht starten“ als „unverständlich starten“ – denn die zweite Situation ist sehr schwer zu diagnostizieren. Die Anwendung ist „lebendig“, der Port offen, aber alles Verhalten ist falsch – und Sie verschwenden eine Stunde mit Rätselraten.

Aus diesem Grund ist optional: nicht nur „Kosmetik“, sondern ein Hebel, der die Strenge des Starts verändert. Er sollte bewusst eingesetzt werden, sonst organisieren Sie sich selbst einen „stillen Fehlermodus“.

3. optional: – was es tut und was nicht

optional: ist ein Präfix, den Sie vor einen Konfigurationsort setzen können. Es bedeutet nur eines: Wenn die Ressource an diesem Ort nicht gefunden wird, muss der Start der Anwendung nicht abgebrochen werden. Das ist alles.

Räumen wir gleich drei populäre Illusionen aus dem Weg.

Erste Illusion: „optional: senkt die Priorität der Konfiguration“. Nein. Wenn die Datei existiert und eingebunden wurde, wirkt sie in der Konfiguration wie gewohnt. Die Prioritäten (wer überschreibt wen) hängen vom Mechanismus ab (import, additional-location, Reihenfolge), und optional: hat keinen Einfluss darauf.

Zweite Illusion: „optional: heilt kaputtes YAML“. Auch nicht. Wenn die Datei existiert, aber darin ein Syntaxfehler im YAML, eine kaputte Kodierung oder einfach eine ungültige Struktur steckt, wird die Anwendung abstürzen. optional: schützt nur vor der Situation „Ressource fehlt“, nicht vor der Situation „Ressource ist da, aber schlecht“.

Dritte Illusion: „optional: ändert die Art der Pfadberechnung“. Das tut es nicht. Ein festgelegter Pfad bleibt festgelegt (file:... / classpath:...), ein relativer bleibt relativ, ein Verzeichnis bleibt ein Verzeichnis. optional: ist rein die Reaktion auf das Fehlen.

Um dies im Kopf zu verankern, kann man sich einen kleinen Algorithmus vorstellen:

flowchart TD
    %% `optional:` wirkt nur auf die Reaktion auf das Fehlen der Ressource
    A["Boot versucht, die Konfiguration vom Ort zu laden"] --> B{"Ressource existiert?"}
    B -->|Ja| C["Liest Datei/Verzeichnis und fügt Eigenschaften hinzu"]
    B -->|Nein| D{"Gibt es das Präfix optional: ?"}
    D -->|Ja| E["Überspringt den Ort und setzt den Start fort"]
    D -->|Nein| F["Startabbruch (fail-fast)"]

Dieses Schema ist bewusst einfach gehalten. In der Realität führt Boot noch viele Schritte rund um Profile und Ebenen aus, aber die Bedeutung von optional: ist genau diese.

4. optional: in spring.config.import: „einbinden, wenn gefunden“

Als wir die Konfiguration über spring.config.import zerlegt haben, haben wir im Grunde gesagt: „Lies application.yaml und binde dann noch dieses eine Dokument ein“. Das ist praktisch für eine modulare Struktur, wirft aber sofort die Frage auf: Soll die importierte Datei obligatorisch sein oder nicht?

Bei obligatorischen Importen ist alles einfach: Wenn die Datei Teil des grundlegenden Konfigurationsmodells der Anwendung ist und der Dienst ohne sie keinen Sinn ergibt, ist es besser, sofort abzubrechen. Wenn wir zum Beispiel die Kursliste in classpath:catalog-data.yaml ausgelagert haben, dann ist das in unserem Projekt catalog-service ein Teil des Artefakts. Fehlt diese Datei, ist der Build kaputt, das Jar unvollständig, oder jemand hat „Ressourcen aufgeräumt“. Einen solchen Start sollte man lieber abbrechen, anstatt zu versuchen, „ohne Katalog“ zu arbeiten.

Beispiel für einen obligatorischen Import:

# src/main/resources/application.yaml
spring:
  config:
    # Obligatorischer Import: Wenn die Datei nicht im Classpath ist, ist das fast immer ein Build-Fehler
    import: "classpath:catalog-data.yaml"

Nun zu den optionalen Importen. Ein typisches Szenario sind lokale/externe Überschreibungen, die existieren können, aber nicht müssen. Wir möchten genau, dass ein Entwickler eine Datei in ./config/ anlegen kann, aber wenn er es nicht tut, startet die Anwendung trotzdem.

Beispiel:

# src/main/resources/application.yaml
spring:
  config:
    # Optionaler Import: Wenn die Datei daneben nicht existiert – überspringen wir die Ebene einfach
    import: "optional:file:./config/catalog-extra.yaml"

Hier gibt es eine wichtige Feinheit: Dies ist kein „zusätzliches Verzeichnis“, sondern eine konkrete Datei. Wenn sie existiert, wird sie gelesen und ihre Eigenschaften fließen in die endgültige Konfiguration ein. Wenn sie nicht existiert, macht Boot einfach weiter.

Ein sehr praktischer Mikro-Anwendungsfall für catalog-service: Sie können lokal den Titel der Anwendung überschreiben, ohne application-local.yaml zu ändern und ohne lokale Änderungen ins Repository zu committen.

Zum Beispiel haben Sie in Ihrer Basis-Konfiguration:

# src/main/resources/application.yaml
app:
  catalog:
    # Basiswert (wird verwendet, wenn es keine externe Override-Ebene gibt)
    title: "Spring+ Catalog"

Und in ./config/catalog-extra.yaml (wenn Sie sie lokal angelegt haben):

# ./config/catalog-extra.yaml
app:
  catalog:
    # Lokaler Override: wird nur angewendet, wenn die Datei existiert und importiert wurde
    title: "Spring+ Catalog (Ivans Laptop-Edition)"

Wenn die Datei existiert, wird der Titel „Spring+ Catalog (Ivans Laptop-Edition)“. Wenn die Datei nicht existiert, bleibt der Basiswert. Keine Magie, nur eine zusätzliche Ebene.

5. optional: und spring.config.location

Wir haben bereits besprochen, dass spring.config.location und spring.config.additional-location nicht die Frage „was importieren“ beantworten, sondern die Frage „wo überhaupt nach der Konfiguration suchen“. Und es gibt noch eine methodische Bedeutung: Diese Eigenschaften werden sehr früh gelesen, daher werden sie meistens von außen gesetzt, nicht innerhalb der application.yaml. Sonst versuchen Sie, die Suchregeln für die Datei … aus der Datei selbst zu ändern, die noch gefunden werden muss. Das ergibt einen Konfigurations-„Quest“.

Und auch hier funktioniert optional:. Wenn Sie ein externes Verzeichnis hinzufügen, das fehlen kann (z.B. in einem frischen Repository-Klon), möchten Sie es fast immer als optional markieren – sonst hat jeder Neue einen „roten Start“ ohne Grund.

Beispiel für eine sanfte Erweiterung der Standardsuche:

# Fügt einen externen Ordner als „weitere Suchebene“ hinzu, ohne den Start zu unterbrechen, wenn er fehlt
./gradlew bootRun --args="--spring.config.additional-location=optional:file:./config/"

Die Bedeutung ist: „Wenn daneben der Ordner ./config/ existiert, versuche dort die Standard-application*.yaml zu finden und sie als externe Override-Ebene anzuwenden; wenn der Ordner nicht existiert – brich den Start nicht ab“.

Wenn Sie statt additional-location die location verwenden, dann ersetzen Sie die Standardsuche. Hier wird optional noch wichtiger, weil Sie versehentlich die gepackte Konfiguration „abschneiden“ und ganz ohne Konfiguration dastehen könnten.

Zum Beispiel:

# Wichtig: location ERSETZT die Standardsuchorte, daher ist dies eine sehr „scharfe“ Einstellung
./gradlew bootRun --args="--spring.config.location=optional:file:./custom-config/"

Wenn das Verzeichnis nicht existiert, stürzen wir nicht ab, ja. Aber es gibt eine Falle: Sie haben die Standardsuche durch ein (potenziell leeres) Verzeichnis ersetzt. In einem solchen Start könnten Sie unerwartet mit Defaults starten und ohne die gewohnten application.yaml aus den Ressourcen. Das ist dann nicht mehr nur ein „sanfter Start“, sondern ein „sanfter Start ins Ungewisse“. Daher wird spring.config.location normalerweise vorsichtig und selten verwendet, und optional: dort ist kein Grund, sich zu entspannen.

6. Fail-fast vs. tolerant startup: Wie wählt man die Strenge?

Die Wahl zwischen fail-fast und sanftem Start läuft nicht auf „Strenge ist gut“ oder „Strenge ist schlecht“ hinaus. Es geht darum, wie wichtig es Ihnen ist, dass die Anwendung nicht ohne eine bestimmte Konfigurationsebene starten darf.

Unten finden Sie eine kleine Tabelle, die hilft, die Entscheidung ohne Rätselraten zu treffen:

Frage an sich selbst Wenn Antwort „Ja“ Wenn Antwort „Nein“
Ohne diese Datei verliert die Anwendung ihren Sinn oder verletzt Anforderungen (z. B. keine Katalogdaten)? Machen Sie die Datei obligatorisch (ohne optional:), der Start soll abbrechen optional: kann in Betracht gezogen werden
Ist diese Datei Teil des Artefakts (liegt in src/main/resources und soll im Jar sein)? Normalerweise obligatorisch: Fehlen ist ein Build-Fehler Wenn es eine externe Ebene ist, kann optional: angemessen sein
Soll diese Datei nur manchmal existieren (lokale Overrides, optionale Entwicklerkomfortfunktionen)? optional: – ausgezeichnete Wahl Eher obligatorisch
Ist es Ihnen wichtig, ein Problem sofort zu bemerken, nicht erst nach 20 Minuten merkwürdigem Verhalten? Fail-fast Tolerant startup nur, wenn Sie die Defaults genau kontrollieren
Können Sie in Worten beschreiben, wie der Dienst ohne diese Datei funktionieren soll? Dann kann optional: sicher sein Wenn „weiß ich nicht“ – lieber fail-fast

Im echten Leben lautet eine gute Faustregel für Junior-Projekte fast grob: Wenn Sie unsicher sind – setzen Sie kein optional:. Denn das Fehlen einer Datei ist eine seltene Situation, während ein „stiller falscher Start“ ein sehr häufiges Problem ist.

Und noch ein wichtiger Punkt: Ein sanfter Start ist nur dann sinnvoll, wenn Sie vernünftige Standardwerte oder andere Quellen haben, die garantiert eine korrekte Konfiguration liefern. Sonst wird optional: zu einem Knopf mit der Aufschrift „Problem ignorieren“.

Wie wendet man das in catalog-service an?

Nun konkret für unser Übungsprojekt. Wir haben mehrere Konfigurationsebenen, jede mit ihrer eigenen Rolle.

application.yaml innerhalb von src/main/resources ist der grundlegende Rahmen. Er muss immer existieren, sonst ist der Dienst einfach nicht das, was wir beschrieben haben. Dort leben spring.application.name, allgemeine Flags und die Deklarationen von import für die modulare Konfiguration. Das ist kein Ort für optional: – das ist das „Skelett“.

catalog-data.yaml (innerhalb der Ressourcen) – das sind die großen Katalogdaten, die wir ausgelagert haben, um aus application.yaml keine Müllhalde zu machen. Diese Datei sollte in der Regel auch obligatorisch sein, denn ohne sie verliert unser reiner Lese-Katalogdienst seinen Sinn. Wenn Sie catalog-service starten und er „quasi lebt“, aber die Kursliste leer ist, dann ist das in einem Übungsprojekt fast immer ein Fehler, kein zulässiger Modus.

Hingegen ist ./config/catalog-extra.yaml ein externer Zusatz. Das ist ein guter Kandidat für optional:. Seine Rolle ist nicht, „die Anwendung funktionsfähig zu machen“, sondern „die Möglichkeit zu geben, das Verhalten zu überschreiben, ohne das Jar zu ändern und ohne lokale Änderungen zu committen“. Wenn die Datei nicht existiert – in Ordnung. Wenn sie existiert – auch in Ordnung.

Beispiel für ein gutes Schema, in dem optional angemessen aussieht:

# src/main/resources/application.yaml
spring:
  application:
    name: catalog-service
  config:
    # Zuerst ist die Datenebene obligatorisch (Teil des Artefakts), dann optionaler externer Override
    import: "classpath:catalog-data.yaml,optional:file:./config/catalog-extra.yaml"

app:
  catalog:
    # Standardwerte (sollten vernünftig sein, sonst wird optional zum „Problem ignorieren“)
    title: "Spring+ Catalog"
    max-featured-count: 4

Hier ist alles mit Worten erklärbar. „Wir haben eine Basis-Konfiguration. Daran wird immer die Datendatei angehängt. Und wenn daneben ein externer Override liegt – kann er einzelne Werte ändern“.

7. Schnelle Diagnose der optional-Ebene

Wenn die Konfiguration mehrschichtig wird, spielt das Gehirn das Spiel „Ich erinnere mich genau, dass ich das in irgendeiner Datei geändert habe“. Und das ist ein sehr menschliches Spiel, aber es endet schlecht. Daher ist es nützlich, einen kleinen diagnostischen Punkt zu haben, der den endgültigen Wert einer Eigenschaft zeigt und nicht Ihre Erinnerung.

Bevor wir in die typsichere Konfiguration abtauchen, können wir das über Environment in unserem StartupSummaryRunner machen. Ein minimales Beispiel:

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
class StartupSummaryRunner implements ApplicationRunner {

    private final Environment env;

    StartupSummaryRunner(Environment env) {
        this.env = env;
    }

    @Override
    public void run(ApplicationArguments args) {
        // Wert aus der endgültigen Environment holen (unter Berücksichtigung aller Ebenen: Basis, Import, optional-Override usw.)
        String title = env.getProperty("app.catalog.title");

        // Schnelle Diagnose: Wir sehen, welcher Wert sich tatsächlich nach der Konfigurationsüberlagerung ergibt
        System.out.println("app.catalog.title = " + title); // app.catalog.title = Spring+ Catalog
    }
}

Jetzt können Sie ein sehr anschauliches Experiment ohne jede Magie durchführen.

Szenario A: Die Datei ./config/catalog-extra.yaml existiert nicht. Dann ist die Ausgabe der Basiswert.

Szenario B: Sie erstellen ./config/catalog-extra.yaml und überschreiben app.catalog.title. Dann ändert sich die Ausgabe. Und Sie sehen sofort, dass die optionale Ebene tatsächlich eingebunden wurde, ohne Rätselraten wie „ich glaube, Boot sollte …“.

Und genau hier taucht der erwachsene Gedanke auf: optional: ist nützlich, aber Diagnose wird immer benötigt. Wenn Sie nicht leicht überprüfen können, ob eine Ebene angewendet wurde oder nicht, verwandeln Sie die Konfiguration in Kaffeesatzleserei. Und Kaffee ist zu schade dafür.

8. Typische Fehler bei der Verwendung von optional:

Fehler Nr. 1: Eine obligatorische Konfigurationsebene optional machen, „damit es nicht abstürzt“.
Eine solche Entscheidung sieht nach schnellem Fortschritt aus: Die Anwendung startet, alle sind zufrieden. Aber nach einer Weile erwischt man die unangenehmste Kategorie von Fehlern – „Es ist gestartet, aber es verhält sich seltsam“. Wenn die Datei wirklich für den korrekten Betrieb obligatorisch ist (Katalogdaten, kritische Umgebungsflags), dann ist fail-fast keine Grausamkeit, sondern eine Fürsorge für Ihre Zeit.

Fehler Nr. 2: optional: als universelles Pflaster verwenden und grundsätzlich alles als optional markieren.
Wenn es viele optionale Orte gibt, verschwimmt die klare Grenze zwischen „Anwendung korrekt gestartet“ und „Anwendung zufällig gestartet“. Letztendlich können Sie nicht mehr mündlich erklären, warum genau dieser Eigenschaftswert zustande gekommen ist. Und wenn sich ein Schema nicht mit Worten erklären lässt, wird es meist sehr teuer im Support.

Fehler Nr. 3: Zu denken, dass optional: die Prioritäten beeinflusst und „die Datei schwächer macht“.
Dies ist eine weit verbreitete mentale Falle: Als ob eine optionale Datei ein „Fallback“ mit niedriger Priorität wäre. Tatsächlich ändert optional: weder die Reihenfolge der Überlagerungsebenen noch die Regel „wer wen überschreibt“. Es ändert nur die Reaktion auf das Fehlen. Wenn die Datei gefunden wird, wirkt sie in der Konfiguration wie eine normale Ebene und kann durchaus Basiswerte überschreiben.

Fehler Nr. 4: Zu erwarten, dass optional: vor kaputtem YAML oder falschem Format rettet.
optional: ist kein „try/catch um das Parsen herum“. Wenn die Datei existiert, aber darin ein Fehler steckt, stürzt Boot ab (und das ist richtig: Sonst bekämen Sie einen Start mit teilweise gelesener Konfiguration). Daher eignet sich optional für „Datei könnte fehlen“, aber nicht für „Datei ist da, aber wir sind uns nicht sicher, ob sie gültig ist“.

Fehler Nr. 5: optional: als Weg nutzen, um unklare Fehler zu verstecken.
Manchmal sieht ein Entwickler, dass Boot sich über eine nicht gefundene Ressource beschwert, und anstatt sich mit dem Pfad oder dem Schichtenmodell zu befassen, fügt er einfach optional: hinzu. Das kann den „roten Start“ beheben, zerstört aber die architektonische Klarheit. Besser ist es, ehrlich zu entscheiden: Ist diese Ressource obligatorisch (dann korrigieren wir den Pfad/die Paketierung) oder ist sie wirklich optional (dann ist optional gerechtfertigt).

1
Aufgabe
Spring Boot, Level 16, Lektion 2
Gesperrt
Verpflichtende externe Datei und Fail-Fast-Start
Verpflichtende externe Datei und Fail-Fast-Start
1
Aufgabe
Spring Boot, Level 16, Lektion 2
Gesperrt
Optionale Datei und Basiswert
Optionale Datei und Basiswert
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION