CodeGym /Kurse /Spring Boot /Konfiguration catalog-service: Schema

Konfiguration catalog-service: Schema

Spring Boot
Level 16, Lektion 4
Verfügbar

1. Konfigurationsschema als Vereinbarung

Wenn Sie jemals ein fremdes Projekt geöffnet und dort fünf Konfigurationsdateien mit denselben Schlüsseln an drei Stellen gesehen haben, kennen Sie das Gefühl: „Es funktioniert, aber warum – fragen Sie nicht“. Das Konfigurationsschema soll genau das Rätselraten beseitigen. Wir möchten, dass jedes Teammitglied die Frage beantworten kann: „Wo liegt die Einstellung X und warum wird genau dieser Wert genommen“.

Das Wichtigste dabei ist zu verstehen, dass Spring Boot uns Mechanismen bietet (import, Profile, externe Orte), aber er kann nicht an unserer Stelle die architektonische Entscheidung treffen „welche Datei wofür verantwortlich ist“. Das heißt, Boot ist nicht „mach mir hübsch“, sondern „hier sind Werkzeuge, bau dir ein verständliches System“. Und genau dieses System werden wir jetzt so bauen, dass es stabil, erweiterbar ist und sich nicht in eine separate Disziplin „Config-Engineering“ verwandelt.

Um es einfacher im Kopf zu behalten, können Sie sich die Konfiguration wie Koffer auf einer Reise vorstellen. Ein riesiger Koffer, in dem Socken, Laptop, Shampoo und Dokumente sind – das ist zwar „alles an einem Ort“, aber den Pass beim Einsteigen in den Zug zu finden, wird zum Abenteuer. Ein Konfigurationsschema ist, wenn Sie die Sachen ehrlich sortieren: Dokumente in eine separate Mappe, Kleidung separat, Reiseapotheke separat. Ja, es gibt mehr Dateien, aber das Leben ist ruhiger.

2. Dateistruktur von catalog-service

Wenn wir „Schema“ sagen, ist es nützlich, sofort die physische Struktur festzulegen. Wir erfinden kein Raumschiff: Wir brauchen einen kleinen Boot-Service ohne DB, aber seine Konfiguration muss trotzdem modular sein. Unser Ziel‑Dateisatz in dieser Phase sieht so aus:

src/main/resources/
├── application.yaml
├── application-local.yaml
├── application-dev.yaml
├── application-prod.yaml
└── catalog-data.yaml

./config/
└── catalog-extra.yaml            (optional, lokaler Override)

Es gibt drei semantische Ebenen: die Basiskonfiguration der Anwendung, die Profilunterschiede und eine separate Datendatei des Katalogs. Außerdem gibt es eine externe „Tasche“ für lokale/private Überschreibungen, die wir weder committen noch in die Profildateien stecken möchten. Wichtig: Die externe Datei ist genau eine Override-Ebene, keine „zweite Kopie der Konfiguration“.

Lassen Sie uns die Verantwortlichkeiten direkt in Worte fassen, damit wir später nicht vom Gedächtnis abhängen.

Artefakt Speicherort Wofür benötigt Welcher Änderungsstil wird erwartet
application.yaml im Jar (resources) Grundrahmen der Anwendung: Name, allgemeine Flags, Import-Deklarationen, Standardwerte wird selten bearbeitet, bewusst, fast immer committet
application-{profile}.yaml im Jar Umgebungsunterschiede: „wie verhält man sich lokal/in dev/in prod“ ändert sich mit dem Projektwachstum, sollte aber nicht die gesamte Konfiguration duplizieren
catalog-data.yaml im Jar großer Datenblock des Katalogs: Kursliste und zugehörige Daten ändert sich, wenn sich die Domänendaten ändern, sollte aber nicht mit Umgebungseinstellungen vermischt werden
./config/catalog-extra.yaml extern (Dateisystem) optionale lokale Override-Ebene, um Ressourcen nicht zu berühren und persönliche Einstellungen nicht zu committen kann ganz fehlen; wenn vorhanden, halten wir es klein

Beachten Sie die feine Sache: catalog-data.yaml ist keine „weitere application-Datei“. Sie existiert, weil die Katalogdaten (Kursliste) vom Umfang und Sinn her anders sind als die Einstellungen der Anwendung selbst. Vereinfacht gesagt: „Wie der Service funktioniert“ und „welche Kurse im Katalog sind“ sind zwei verschiedene Themen, und es ist sinnvoll, sie getrennt zu halten.

3. Ebenen und Prioritäten der Konfiguration

Wenn die Konfiguration modular wird, lautet die Hauptangst eines Anfängers: „Wie soll ich wissen, wer gewinnt, wenn dieselbe Eigenschaft an mehreren Stellen gesetzt ist?“ Die richtige Antwort: Idealerweise dieselbe Eigenschaft nicht grundlos an mehreren Stellen setzen. Precedence ist kein Werkzeug für die tägliche Arbeit, sondern ein Sicherheitsnetz. In einem gesunden Schema hat die Mehrheit der Schlüssel einen „einzigen Besitzer“.

Daher ist es für catalog-service nützlicher, nicht eine universelle Leiter aller möglichen Sources der Welt im Kopf zu haben, sondern eine kanonische Rollenverteilung. Sie beantwortet nicht die Frage „wer ist absolut über allen“, sondern „welche Datei ist in unserem Projekt wofür verantwortlich“.

Ebene / Datei Rolle in catalog-service Was lebt dort
application.yaml Grundrahmen der Anwendung spring.application.name, allgemeine Defaults, Deklarationen spring.config.import
catalog-data.yaml separater Datenblock, eingebunden über spring.config.import Kursliste und zugehörige Katalogdaten
application-{profile}.yaml Profilunterschiede nur das, was wirklich von local/dev/prod abhängt
./config/catalog-extra.yaml externer optionaler Override via explizitem file:-Import persönliche lokale Überschreibungen ohne Änderung der Ressourcen und ohne Commit

Eine solche Konstellation kann man leicht laut aussprechen: Die Basis lebt in application.yaml, große Daten sind in eine separate importierte Datei ausgelagert, Profildokumente ändern nur die Umgebungsunterschiede, und das externe catalog-extra.yaml ist eine kleine lokale Komfortschicht. Sobald ein und derselbe Schlüssel unkontrolliert zwischen diesen Dateien wandert, wird das Schema zur Lotterie.

Auf diese Dateiverteilung können wie zuvor Env Vars, System Properties und CLI Args draufkommen. Sie können immer noch Werte überschreiben, aber das sind separate Kanäle der Laufzeitsteuerung. In das kanonische Dateischema von catalog-service nehmen wir sie nicht auf, um die Dokumentverteilung nicht mit externer Startsteuerung zu vermischen.

Verzeichnisse über spring.config.additional-location und Wildcard sind bereits eine Erweiterungsmöglichkeit dieses Schemas, kein obligatorischer Teil. Sie sind nützlich, wenn ein externes Paket mit Standard-application*.yaml benötigt wird, aber der grundlegende Projekt-Snapshot basiert nicht darauf.

4. Basis-application.yaml und Import

Ein Basis-application.yaml in einem gesunden Projekt ist wie ein Inhaltsverzeichnis in einem Buch. Es sollte nicht das ganze Buch enthalten, seine Aufgabe ist es, einen Rahmen zu setzen: Wie heißt die Anwendung, welche Basiseinstellungen hat sie, welche zusätzlichen Konfigurationsteile müssen eingebunden werden. Wenn Sie das im Kopf behalten, hört die Hand auf, „noch etwas ins Base zu schreiben“, und die Dateien beginnen, ihre Rolle zu leben, nicht die Geschichte zufälliger Änderungen.

In unserem catalog-service sollte die Basisdatendatei kurz sein und leicht überflogen werden können. Wir lassen darin spring.application.name, unsere benutzerdefinierten Eigenschaften app.catalog.* (nur die, die wirklich gemeinsam sind) und spring.config.import für zwei Dinge: catalog-data.yaml aus dem Classpath und eine externe optionale Override-Datei.

Beispiel der Basis-application.yaml:

# src/main/resources/application.yaml

spring:
  application:
    # Dienstname (beeinflusst Logs, Metriken, viele Autokonfigurationen)
    name: catalog-service
  config:
    # Zusätzliche Konfigurationsquellen einbinden.
    # Wichtig: Die Reihenfolge im Import beeinflusst, was was überschreiben kann.
    # 1) Katalogdaten (im Jar)
    # 2) Lokaler externer Override (kann fehlen)
    import: "classpath:catalog-data.yaml,optional:file:./config/catalog-extra.yaml"

app:
  catalog:
    # Gemeinsame Standardwerte (unabhängig von der Umgebung)
    title: "Spring+ Catalog"
    max-featured-count: 4
    default-published-only: true
    # Praktisches Flag für den Start: kleines Reporting ausgeben (keine Geheimnisse)
    startup-report-enabled: true

Beachten Sie zwei wichtige Ideen.

Erstens: catalog-data.yaml wird nicht automatisch eingelesen, weil es keine application*.yaml ist. Es ist eine „normale Datei“, und um sie Teil der Konfiguration zu machen, binden wir sie über spring.config.import ein. Ja, sie liegt in resources, aber Boot liest nicht jede YAML in resources aus Neugier.

Zweitens: Die externe Datei binden wir als optional:file:... ein. Das bedeutet, sie kann auf Ihrem Rechner existieren, aber bei einem Kollegen oder auf CI nicht – und die Welt sollte nicht untergehen. Wir wählen bewusst einen „sanften Start“ für diese Ebene, weil es eine lokale praktische Erweiterung ist, kein obligatorischer Vertragsbestandteil der Anwendung.

Es gibt noch eine feine Sache: Eine importierte Datei (wenn sie dieselben Schlüssel enthält) kann Werte aus application.yaml überschreiben. Daher ist der gesündeste Stil, Schlüssel nicht zu duplizieren zwischen Base und importierter Datei. Und das machen wir auch: Base hält den Rahmen, catalog-data.yaml hält die Daten, nicht die Basis-Flags.

5. Profile und externe Überschreibungen

Profile kann man sehr leicht als „Kopie der gesamten Konfiguration für jede Umgebung“ nutzen. Das erscheint die ersten zwei Tage logisch, aber am dritten Tag ändert man plötzlich dieselbe Einstellung in drei Dateien und vergisst die vierte. Daher halten wir uns an das Prinzip: Profildateien ändern nur das, was wirklich von der Umgebung abhängt. Alles andere bleibt im Base oder in den importierten Datendateien.

Beginnen wir mit dem Sichersten: app.catalog.title ändern, um visuell sofort zu sehen, ob Sie in local, dev oder prod sind. Das ist banal, aber es ist ein hervorragender „Umgebungsmarker“ ohne schwere Infrastruktur.

Beispiel application-local.yaml:

# src/main/resources/application-local.yaml
app:
  catalog:
    # Umgebungsmarker: um in der UI/Logs local und dev/prod nicht zu verwechseln
    title: "Spring+ Catalog (local)"

Beispiel application-dev.yaml:

# src/main/resources/application-dev.yaml
app:
  catalog:
    # Umgebungsmarker: dev
    title: "Spring+ Catalog (dev)"

Beispiel application-prod.yaml:

# src/main/resources/application-prod.yaml
app:
  catalog:
    # In prod wollen wir normalerweise keine „lustigen“ Suffixe
    title: "Spring+ Catalog"

Beachten Sie, dass wir hier nicht max-featured-count, default-published-only und schon gar nicht die Kursliste kopieren. Profile sind nicht „der Ort, wo man alles bequem dupliziert“, sondern „der Ort, wo man vorsichtig nur das Nötige ändert“.

Nun zur externen Override-Datei ./config/catalog-extra.yaml. Ihre Rolle sind punktuelle Änderungen ohne Änderung der resources. Typisches Szenario: Sie möchten lokal das Featured-Limit erhöhen, den Startbericht vorübergehend deaktivieren oder einen „eigenen“ Kursbestand für eine Demo einlegen – und das nicht committen.

Beispiel einer externen Datei:

# ./config/catalog-extra.yaml
app:
  catalog:
    # Lokaler Override: man kann Parameter vorübergehend anpassen, ohne die Ressourcen im Jar zu berühren
    max-featured-count: 6
    startup-report-enabled: false

Diese Datei wird über den Import eingebunden, daher muss sie nicht application.yaml heißen. Das ist ein wichtiger Unterschied zu dem „Konfigurationsverzeichnis“, das Boot auf Standardnamen scannt. catalog-extra.yaml ist also ein beliebiger Name und wird nicht „magisch sichtbar“, wenn Sie ihn einfach danebenlegen. Wir haben ihn sichtbar gemacht, indem wir in spring.config.import eine konkrete file:-Lokation angegeben haben.

Damit ist das kanonische Schema von catalog-service bereits aufgebaut: application.yaml als Rahmen, catalog-data.yaml als Datenblock, Profildateien als Umgebungs-Deltas und ./config/catalog-extra.yaml als kleiner lokaler Override. Normalerweise reicht das aus. Es ist nicht sinnvoll, grundlos die Directory-Search-Mechanik hinzuzumischen: expliziter Import einer beliebigen Datei und die Suche nach Standard-application*.yaml lösen verschiedene Aufgaben.

Wenn nicht eine punktuelle Datei, sondern ein ganzes externes Verzeichnis mit Standard-application*.yaml benötigt wird, kommt eine separate Variante über spring.config.additional-location zum Einsatz. Das ist kein grundlegender Projekt-Snapshot mehr, sondern eine Erweiterung für Starts, bei denen Konfigurationen als Ordner kommen.

Zum Beispiel könnte ein Start mit einem separaten Verzeichnis für Standardkonfigurationen so aussehen:

# Externes Verzeichnis mit Standard-application*.yaml
./gradlew bootRun --args="--spring.profiles.active=local --spring.config.additional-location=optional:file:./custom-config/"

Hier sucht Boot genau nach application.yaml, application-local.yaml und anderen Standardnamen in ./custom-config/. Das ersetzt nicht unser ./config/catalog-extra.yaml, sondern ist eine andere Möglichkeit, eine externe Ebene zu organisieren.

Wenn solche Konfigurationen auf mehrere Unterverzeichnisse der ersten Ebene verteilt sind, kann man einen Wildcard verwenden:

# Mehrere externe Unterverzeichnisse mit Standard-application*.yaml
./gradlew bootRun --args="--spring.config.additional-location=optional:file:./custom-config/*/"

Das ist praktisch, aber die Reihenfolge der Ebenen hängt dann von den Verzeichnisnamen ab. Daher sollte man den Wildcard eher als praktische Erweiterung betrachten, nicht als Kanon des Projekts. Wenn die Reihenfolge entscheidend ist, ist eine explizite Liste von Verzeichnissen normalerweise sicherer als die Hoffnung auf das Alphabet.

Diagnose der endgültigen Konfiguration

Wenn die Konfiguration mehrschichtig wird, beginnt das Gehirn zu versuchen, das Verhalten von Boot im Kopf zu „simulieren“, und das endet meist mit dem Satz „naja, so funktioniert es wahrscheinlich“. In diesem Moment ist es nützlich, einen kleinen diagnostischen Anker hinzuzufügen: ein Code-Stück, das beim Start die endgültigen Werte einiger Schlüssel ausgibt. Nicht um das Verständnis von Logs und Actuator zu ersetzen (das kommt später), sondern um im Moment nicht zu raten, welche Ebene gewonnen hat.

Wir haben bereits einen geeigneten Ort: den StartupSummaryRunner (oder einen beliebigen ApplicationRunner), der in catalog.bootstrap lebt. Bis wir zum normalen Logging kommen, können wir System.out.println als temporäre „Taschenlampe“ verwenden. Hauptsache, wir machen daraus keine ewige println-Religion und geben keine Geheimnisse aus.

Beispiel eines sehr kompakten Runners:

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) {
        // Environment enthält bereits die endgültige „Verklebung“ aller Konfigurationsebenen
        this.env = env;
    }

    @Override
    public void run(ApplicationArguments args) {
        // Endgültige Werte lesen: falls Schlüssel nicht gefunden – Standard verwenden
        String title = env.getProperty("app.catalog.title", "Catalog");

        // Nützlich: aktive Profile sehen, um nicht zu raten „warum hat das Falsche gezogen“
        String profiles = String.join(",", env.getActiveProfiles());

        // Wichtig: Geben Sie keine Geheimnisse (Passwörter, Tokens) per println aus, nicht einmal „vorübergehend“
        System.out.println("Active profiles = " + profiles); // Active profiles = local
        System.out.println("app.catalog.title = " + title);   // app.catalog.title = Spring+ Catalog (local)
    }
}

Was bringt uns das praktisch? Wenn Sie die Anwendung mit --spring.profiles.active=local starten und gleichzeitig die Profildatei oder den externen Override ändern, sehen Sie sofort das Ergebnis „nach dem Zusammenbau aller Ebenen“. Und wenn plötzlich der title nicht der erwartete ist, rennen Sie nicht sofort zu StackOverflow, sondern beginnen, das Schema zu prüfen: Welche Ebene sollte diesen Schlüssel besitzen, haben Sie ihn an zwei Stellen gesetzt, hat die externe Datei überschrieben.

Übrigens illustriert dieselbe Stelle hervorragend, warum wir so beharrlich auf „einem Besitzer des Schlüssels“ bestehen. Wenn Sie app.catalog.title gleichzeitig in application.yaml, in application-local.yaml und auch noch in catalog-extra.yaml halten, wird der Runner zwar das Endergebnis ausgeben, aber zu erklären, warum es genau so ist, wird schwer. Wenn sich der Titel dagegen nur durch das Profil ändert, braucht die Erklärung einen Satz, und Sie werden nicht zum YAML-Archäologen.

6. Typische Fehler beim Aufbau des Konfigurationsschemas

Fehler Nr. 1: Derselbe Schlüssel lebt „für alle Fälle“ in drei Dateien.
Das ist der häufigste Grund für Konfigurationsmysterien. Technisch wählt Spring Boot ehrlich den Gewinner nach den Prioritätsregeln, aber Sie selbst werden das System nicht mehr verstehen. Wenn ein Schlüssel von der Umgebung abhängen soll, lass ihn in den Profildateien leben. Wenn er allgemein ist, lass ihn im Base leben. Wenn er lokal-persönlich ist, lass ihn im externen Override leben. Das Schema muss erklärbar sein, nicht „durch glücklichen Zufall funktionierend“.

Fehler Nr. 2: Die Kursliste in application-local.yaml schleppen, weil „es für die Lokalumgebung so bequemer ist“.
Profildateien sind kein Ort für große Datenblöcke. Sobald Sie die Kursliste dort ablegen, haben Sie eine zweite „Wahrheitsquelle“, und später eine dritte. Am Ende testen Sie auf einem Datensatz, und ein anderer Entwickler auf einem anderen, und Sie streiten sich nicht über den Code, sondern über die Realität. Große Katalogdaten sollten in catalog-data.yaml leben, und Profile sollten nur das Umgebungsverhalten ändern.

Fehler Nr. 3: Erwarten, dass catalog-extra.yaml automatisch eingelesen wird, nur weil es in ./config/ liegt.
Ein Verzeichnis, das als Config Location eingebunden ist, sucht nach Standard-application*.yaml. Beliebige Namen wie catalog-extra.yaml „errät“ es nicht. Wenn Sie einen beliebigen Namen verwenden möchten, müssen Sie ihn entweder über spring.config.import einbinden (wie wir es gemacht haben) oder über eine genaue spring.config.location/additional-location mit Angabe der konkreten Datei. Auf „vielleicht errät es Boot“ zu hoffen, ist eine schlechte Strategie: Boot errät genau das, was Sie ihm explizit gesagt haben.

Fehler Nr. 4: spring.config.location in die application.yaml legen, die Sie über die Location finden wollen.
Das ist, als würde man an die Tür schreiben: „Der Schlüssel für diese Tür liegt im Raum“. spring.config.location und spring.config.additional-location werden sehr früh gelesen, und normalerweise gibt man sie von außen vor. Wenn Sie die Suchorte verwalten müssen, tun Sie das über Startargumente, Env Vars oder System Properties, sonst erhalten Sie eine unkontrollierbare Konstruktion, bei der die „Einstellung der Einstellung“ versucht geladen zu werden, nachdem die Einstellung bereits benötigt wurde.

Fehler Nr. 5: Eine obligatorische externe Datei mit optional: versehen (oder umgekehrt) ohne bewusste Entscheidung.
optional: ist keine Dekoration. Wenn die Anwendung ohne die externe Datei nicht dem erwarteten Modell entspricht (z. B. eine obligatorische Ebene, ohne die nicht gestartet werden kann), dann sollte das Fehlen der Datei den Start verhindern, und optional: wäre schädlich. Wenn es sich um eine lokale Erweiterung handelt („ich möchte sie einlegen“), dann macht optional: den Start bequemer und ehrlicher. Diese beiden Ansätze zu vermischen bedeutet, einen „erfolgreichen Start“ mit falschem Verhalten zu erhalten, und das ist viel schlimmer als ein ehrlicher Abbruch beim Start.

1
Aufgabe
Spring Boot, Level 16, Lektion 4
Gesperrt
Basisdatei, Profil und importierte Data-Datei
Basisdatei, Profil und importierte Data-Datei
1
Aufgabe
Spring Boot, Level 16, Lektion 4
Gesperrt
Vollständiges Schema mit profile, import und optionalem externen Override
Vollständiges Schema mit profile, import und optionalem externen Override
1
Umfrage/Quiz
Konfigurationsimport, Level 16, Lektion 4
Nicht verfügbar
Konfigurationsimport
Pfade und Prioritäten
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION