CodeGym /Kurse /Spring Core /Package-Struktur für das Scanning

Package-Struktur für das Scanning

Spring Core
Level 5, Lektion 3
Verfügbar

1. Package-Struktur als Teil von DI

Sobald die Scanning-Grenze durch Packages festgelegt wird, hört die Struktur auf, eine Frage des Geschmacks zu sein. Wo eine Klasse liegt, entscheidet darüber, ob der Container sie findet und ob die Anwendung überhaupt startet.

Ehrlich gesagt behandeln viele von uns Packages vor Spring ungefähr so wie Ordner auf dem Desktop: „Hauptsache, es kompiliert — Ordnung kommt irgendwann". Aber sobald du Component-Scanning einschaltest, beginnt der Container, Klassen in Packages zu suchen, und das Chaos in der Struktur hört sofort auf, ein ästhetisches Problem zu sein — es wird ein technisches. Plötzlich stellt sich heraus, dass „wohin du eine Datei gelegt hast" bestimmt, ob der Container sie sieht und ob die Anwendung überhaupt startet.

Wenn Scanning eingeschaltet ist, macht Spring eine sehr einfache Sache: Es geht durch das angegebene Root-Package und seine Unterpakete, findet Klassen mit Stereotype-Annotationen und registriert sie als BeanDefinition. Das heißt, Packages sind nicht nur Namen, sondern eine Landkarte, auf der der Container-Sucher „spazieren geht". Ist die Karte merkwürdig gezeichnet, findet der Sucher entweder nicht das Richtige oder findet zu viel.

Stell dir vor, @ComponentScan ist ein Saugroboter. Du sagst ihm: „Sauge in diesem Zimmer". Wenn wichtige Klassen im Flur geblieben sind — sieht der Container sie nicht, und du schimpfst auf die Technik, obwohl du selbst schuld bist. Und wenn du sagst: „Sauge den ganzen Planeten Erde", fährt der Roboter irgendwo in die Antarktis, erschöpft sich und bringt Pinguine mit — also überflüssige Komponenten.

2. Root-Package als Wurzel der Anwendung

Wenn wir vom „Root-Package der Anwendung" sprechen, wählen wir eigentlich, wo unser Code endet und alles andere beginnt. In der Spring-Welt ist das besonders wichtig, weil Scanning von Natur aus rekursiv ist: Der Container geht in Unterpakete hinein, als würde er einen Ordner öffnen und alles darin ansehen. Daher ist der sicherste und verständlichste Ansatz für ein Lernprojekt — und für die meisten kleinen echten Services auch — ein einziges Root-Package zu haben, in dem alles lebt, was zur Anwendung gehört.

Für ContextFlow ist das Root-Package laut Plan com.example.contextflow. Alles, was wir dem Scanning überlassen wollen, muss in Unterpakete dieser Wurzel landen: com.example.contextflow.application..., com.example.contextflow.infrastructure... und so weiter. Das hat auch einen psychologischen Vorteil: Du öffnest das Projekt — und siehst sofort die Grenzen des Territoriums. Kein Rätselraten, wo „unser Bereich" ist.

Beispiel einer minimalen Scanning-Konfiguration — schon bekannt, aber hier ist gerade die Wurzel wichtig:

package com.example.contextflow.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration // Wir sagen Spring: das ist eine Konfigurationsklasse, aus ihr bauen wir den Kontext
@ComponentScan(basePackages = "com.example.contextflow") // Scanning-Wurzel: alles unterhalb dieses Packages wird durchsucht
public class AppConfig {
    // Hier können @Bean-Methoden stehen, wenn wir Beans manuell registrieren wollen
}

Hier ist die Zeile com.example.contextflow buchstäblich ein „Zaun". Alles innerhalb kann potenziell ein Bean werden, wenn es annotiert ist. Alles außerhalb berührt der Container standardmäßig nicht.

Und ja, das ähnelt etwas der Wahl des Projektordners in der IDE: Wenn du anfängst, wichtige Klassen in com.example.somethingElse abzulegen, jagst du sie selbst hinter den Zaun. Spring ist nicht böse — er ist einfach ehrlich.

3. Vier Zonen: domain, application, infrastructure, config

Damit die Package-Struktur wirklich hilft, muss sie schon am Package-Pfad die Frage beantworten: „Wer bist du?" In dieser Phase des Kurses brauchen wir keinen Architektur-Traktat auf 300 Seiten, aber eine minimale Disziplin brauchen wir. Gute Neuigkeit: Selbst eine einfache Aufteilung in vier Zonen erhöht die Lesbarkeit und Vorhersagbarkeit des Scanning deutlich — besonders für einen Anfänger, der noch nicht „einen Blick" für das Projekt in 30 Sekunden hat.

Unten findest du ein grundlegendes Schema, das gut zu ContextFlow und unseren aktuellen Themen passt:

Zone Beispiel-Package Was liegt dort Häufigster Stereotype
domain com.example.contextflow.domain.model gewöhnliche Domain-Klassen (Order, Customer) üblicherweise ohne Annotationen
application com.example.contextflow.application.service Services für Szenarien und Orchestrierung @Service / manchmal @Component
infrastructure com.example.contextflow.infrastructure.store technische Implementierungen von Ports (In-Memory-Store, Console-Audit) @Repository / @Component
config com.example.contextflow.config Einstiegskonfiguration, Startpunkt @Configuration

Der Kerngedanke hier ist nicht, dass das „die einzig richtige Architektur" ist. Wir machen ein Lernprojekt, und uns ist etwas anderes wichtig: dass am Package-Pfad erkennbar ist, was das für eine Klasse ist, und dass sich Scanning mit einer einzigen verständlichen Wurzel einschalten lässt.

Und noch ein wichtiges Detail: domain.model sollte normalerweise nicht zur Sammelgrube für Beans werden. Domain-Entitäten sind einfach Objekte, die im Verlauf eines Business-Szenarios erstellt werden, zum Beispiel über new Order(...) innerhalb eines Services, und sie leben nicht dauerhaft im Container. Wenn du anfängst, @Component auf Order zu hängen, ist das zumindest seltsam: Eine Bestellung ist kein Service, sie kann nicht einmal erstellt und überall verwendet werden. Sie entsteht viele Male und jedes Mal mit anderen Daten.

4. Beispiel: Package-Baum von ContextFlow

Damit wir Package-Struktur nicht als abstrakte Philosophie diskutieren, schauen wir uns ein sehr bodenständiges Bild an: wie das Projekt im Dateisystem und im Package-Namen aussehen sollte. In diesem Schritt reicht uns eine kleine Struktur, ohne zukünftige Komplizierungen — nur damit Scanning vorhersagbar funktioniert und der Code ohne unnötige Archäologie lesbar ist.

Hier ist ein Beispiel eines minimalen Package-Baums für die aktuelle Phase von ContextFlow:

com.example.contextflow
├── domain
│   └── model
│       ├── Order.java
│       └── Customer.java
├── application
│   ├── service
│   │   ├── OrderPlacementService.java
│   │   └── OrderPricingService.java
│   └── scenario
│       └── ScenarioRunner.java
├── infrastructure
│   ├── store
│   │   └── InMemoryOrderStore.java
│   ├── audit
│   │   └── ConsoleAuditWriter.java
│   └── notification
│       └── ConsoleNotificationSender.java
└── config
    └── AppConfig.java

Beachte, dass dieser Baum gleichzeitig zwei Probleme löst. Erstens wird Scanning einfach: Wir scannen com.example.contextflow, und der Container sieht application und infrastructure. Zweitens wird die Struktur lesbar: Wenn du infrastructure.store.InMemoryOrderStore siehst, musst du nicht den ganzen Code lesen, um die Verantwortung der Klasse zu erahnen — sie ist bereits im Package-Namen eingebaut.

Wir halten diese Fragmente absichtlich minimal: Hier geht es darum, die Aufteilung nach Packages zu sehen, nicht darum, ein vollständiges funktionierendes Bestellungs-Szenario zu bauen.

Lass uns das mit kleinen Code-Fragmenten festigen. Die Domain-Klasse — ohne Stereotype:

package com.example.contextflow.domain.model;

public class Order {
    // Domain-Entität: das ist kein Bean, sondern ein gewöhnliches Objekt mit Daten
    private final String id;

    public Order(String id) {
        // Domain-Objekte werden üblicherweise im Verlauf eines Szenarios erstellt, nicht als Singleton im Kontext gehalten
        this.id = id;
    }

    public String id() {
        // Einfacher Getter: der Domain-Layer muss nichts über Spring wissen
        return id;
    }
}

Die Service-Klasse — @Service:

package com.example.contextflow.application.service;

import org.springframework.stereotype.Service;

@Service // Application-Service: Spring erstellt eine Instanz und injiziert sie dort, wo sie gebraucht wird
public class OrderPricingService {

    public int calculatePrice(int itemsCount) {
        // Fiktive Business-Logik: wichtig ist, dass das Teil des Application-Layers ist
        return itemsCount * 100; // vereinfacht, damit es einfach bleibt
    }
}

Die Infrastruktur-Implementierung des Stores — @Repository:

package com.example.contextflow.infrastructure.store;

import org.springframework.stereotype.Repository;

@Repository // Infrastructure: technisches Detail (Speicherung), das sich leicht durch andere Implementierungen ersetzen lässt
public class InMemoryOrderStore {

    public void save(String orderId) {
        // Für das Beispiel schreiben wir einfach in die Konsole, aber in der Realität könnte hier Map/DB/Datei usw. stehen
        System.out.println("Saved orderId=" + orderId); // Saved orderId=...
    }
}

Der Szenario-Einstieg (Runner) — hier reicht @Component:

package com.example.contextflow.application.scenario;

import org.springframework.stereotype.Component;

@Component // "Szenario-Startpunkt": sei eine Komponente, damit sie aus dem Kontext geholt werden kann
public class ScenarioRunner {

    public void run() {
        // Zeigt, dass der Bean gefunden wurde und das Szenario starten kann
        System.out.println("ContextFlow started"); // ContextFlow started
    }
}

Aus Scanning-Sicht sieht das alles sehr „langweilig" aus — und das ist ein Kompliment. Langweiliges Scanning bedeutet normalerweise einen vorhersagbaren Start.

5. Package-Struktur und Startfehler

Jetzt kommt ein feiner Punkt: Die Package-Struktur ist nicht nur deshalb wichtig, weil sie schön aussieht. Sie beeinflusst direkt, welche Fehler du siehst und wie schnell du sie diagnostizieren kannst. Wir haben bereits gesehen, dass ein zu enger oder zu breiter basePackages den Start auf unterschiedliche Weise bricht. Aber selbst bei richtig gewählter Grenze kann man das Projekt schwer machen: Wenn Klassen so verteilt sind, dass unklar ist, wo was liegt, verbringst du Zeit nicht mit der Lösung des Problems, sondern mit der Suche nach der richtigen Datei.

Die häufigste praktische Situation sieht so aus: Du hast eine neue Klasse hinzugefügt, brav @Service geschrieben, sogar den Konstruktor schön gemacht — und der Container meckert beim Start, dass der Bean nicht gefunden wurde. Ein Anfänger beginnt in diesem Moment den Service zu „reparieren": fügt @Autowired hinzu, schreibt den Konstruktor um, manchmal versucht er sogar gedanklich Spring neu zu installieren. Dabei liegt das Problem ganz woanders: Die Klasse liegt einfach am falschen Ort, also außerhalb des gescannten Package-Baums. Und das lässt sich in fünf Sekunden erkennen, wenn die Projektstruktur lesbar ist.

Zeigen wir einen typischen Fehler am Beispiel. Angenommen, du hast aus Versehen ConsoleAuditWriter in das Package com.example.audit gelegt — außerhalb der Wurzel com.example.contextflow — und das Scanning unverändert gelassen:

@Configuration // Kontext-Konfiguration
@ComponentScan(basePackages = "com.example.contextflow") // Wir scannen nur den Baum com.example.contextflow
public class AppConfig {
    // Alles, was außerhalb von com.example.contextflow liegt, landet hier nicht automatisch
}

Der Container ist schlicht nicht verpflichtet, com.example.audit.ConsoleAuditWriter zu sehen. Dieses Package scannt er nicht. Wenn dann irgendein Service versucht, AuditWriter zu bekommen — egal wie, ob über Konstruktor oder Setter — siehst du alte Bekannte: NoSuchBeanDefinitionException oder UnsatisfiedDependencyException. Und das ist kein „kompliziertes Spring-Problem", sondern einfach „die Klasse liegt hinter dem Zaun".

Betrachtet man Scanning als Route, lässt es sich so darstellen:

flowchart TD
    A["@ComponentScan: com.example.contextflow"] --> B["application.*"]
    A --> C["infrastructure.*"]
    A --> D["domain.* (üblicherweise ohne @Component)"]
    A -. NICHT GESCANNT .-> X["com.example.audit.* (außerhalb root)"]

Die Diagnose in solchen Situationen sollte nicht über „magische Annotationen" laufen, sondern über sehr bodenständige Fragen: „In welchem Package liegt die Klasse?" und „Fällt dieses Package unter das Scanning?"

6. Packages als Navigation im Projekt

Wenn das Projekt wächst, wird das Lesen von Code zu einer eigenen Fähigkeit: Du gehst immer seltener jede Zeile der Reihe nach durch und baust stattdessen immer öfter eine Karte im Kopf. Hier funktioniert die Package-Struktur wie Navigation in einer Stadt. Wenn eine Stadt Straßen mit Namen wie „Bäckerei", „Apotheke" und „Park" hat, errätst du ohne GPS, wo du Brot kaufst. Aber wenn alles „Straße 1", „Straße 2" und „Straße 3" heißt, ist es ohne Begleiter — oder ein sehr geduldiges Gehirn — schon schwierig.

In ContextFlow wollen wir, dass die Rolle einer Klasse in zwei Schritten lesbar ist: zuerst am Package, dann an der Stereotype-Annotation. Zum Beispiel, wenn du siehst:

com.example.contextflow.application.service.OrderPlacementService + @Service

dann verstehst du fast sicher, dass das ein Application-Service für ein Szenario ist. Und wenn du siehst:

com.example.contextflow.infrastructure.store.InMemoryOrderStore + @Repository

dann verstehst du, dass das eine technische Speicher-Implementierung ist.

Das bringt einige praktische Vorteile.

Erster Vorteil — Gespräche im Team. „In infrastructure.notification liegen die Benachrichtigungs-Sender" zu sagen ist viel einfacher als „na, das ist irgendwo in den Utils, aber nicht in diesen Utils, sondern in anderen Utils…".

Zweiter Vorteil — Lokalisierung von Änderungen. Wenn du die Speicherung änderst, berührst du domain kaum und application selten. Das ist keine „saubere Architektur", sondern normaler gesunder Menschenverstand: weniger zufällige Änderungen — weniger zufällige Bugs.

Dritter Vorteil — weniger Gefühl von Magie. Scanning fühlt sich genau solange wie Magie an, bis du verstehst, durch welche Ordner es geht und welche Rollen du in jeder Zone erwartest.

7. Anti-Pattern: ein Package und util/common/misc

Es gibt solche Packages, die früher oder später in fast jedem Projekt auftauchen, wenn man ihnen nicht rechtzeitig Einhalt gebietet. Es beginnt alles meistens mit der guten Idee „lass uns dort alles Gemeinsame ablegen", und endet damit, dass dort wirklich alles landet, für das kein ordentlicher Platz gefunden wurde. Am Ende erinnert das Package an eine Küchenschublade, in der Löffel, Batterien, jemandes vergessener USB-Stick und die Bedienungsanleitung der Mikrowelle von 2011 liegen. Es heißt meistens util, common, misc oder helpers.

Aus Scanning-Sicht sind solche Packages gefährlich, weil sie den Container zur Lotterie machen: Du schaltest Scanning ein — und irgendwelches „Gemeinsames" landet im Kontext. Was genau? Warum wurde es ein Bean? Nicht ganz klar. Und aus Sicht der Projekt-Lesbarkeit sind sie noch schlechter: util erklärt keine Verantwortung, es teilt ehrlich nur eines mit — „hier wurde alles abgelegt, für das man sich nichts einfallen ließ".

Wenn man unbedingt „Gemeinsames" haben möchte, ist es besser, das ehrlich zu machen: das Package nach Verantwortung zu benennen. Wenn du zum Beispiel Klassen rund um Zeit hast, ist support.time oder infrastructure.time verständlicher als util. Bei String-Formatierung — support.format oder infrastructure.format. Hauptsache, der Package-Name beantwortet die Frage „Was lebt hier?", nicht „Wir hatten keine Lust mehr nachzudenken".

Es gibt auch ein anderes Anti-Pattern, das bei Anfängern besonders häufig ist: „Lass uns alles in ein Package service legen". Das scheint praktisch, genau bis zu dem Moment, wo im service 25 Klassen sind und du anfängst, Dateien zufällig zu öffnen, als würdest du raten, wo der gesuchte Schlüssel versteckt ist.

Unser Ziel jetzt ist nicht eine perfekte Architektur. Unser Ziel ist eine Struktur, die dem Scanning und dem Gehirn hilft. Und eine einfache Aufteilung in domain/application/infrastructure/config ist fast immer besser als ein Chaos-Haufen.

Konflikte bei Bean-Namen

Es gibt einen unangenehmen Moment: Der Bean-Name enthält standardmäßig kein Package. Er wird aus dem einfachen Klassennamen abgeleitet. Das heißt, com.example.contextflow.infrastructure.audit.ConsoleWriter und com.example.contextflow.infrastructure.notification.ConsoleWriter wollen standardmäßig beide ein Bean mit dem Namen consoleWriter werden. Packages sind unterschiedlich, der Bean-Name aber derselbe. Dem Container ist das nicht weniger lästig.

Das ist kein Grund zur Panik, aber ein guter Anlass, eine einfache Regel im Hinterkopf zu behalten: Wenn du Scanning verwendest, versuche, keine gleichnamigen einfachen Namen für Komponenten zu häufen. Und wenn es doch passiert ist, kann man den Namen explizit angeben:

package com.example.contextflow.infrastructure.audit;

import org.springframework.stereotype.Component;

@Component("auditConsoleWriter") // Expliziter Bean-Name, um Konflikte mit anderen ConsoleWriter zu vermeiden
public class ConsoleWriter {

    public void write(String msg) {
        // Hier zeigen wir explizit den Zweck: das ist Audit-Ausgabe
        System.out.println("[AUDIT] " + msg);
    }
}

Und analog für Benachrichtigungen:

package com.example.contextflow.infrastructure.notification;

import org.springframework.stereotype.Component;

@Component("notificationConsoleWriter") // Anderer expliziter Bean-Name für Benachrichtigungen
public class ConsoleWriter {

    public void send(String msg) {
        // Hier ist der Zweck ein anderer: Benachrichtigungen, nicht Audit
        System.out.println("[NOTIFY] " + msg);
    }
}

Ja, das ist etwas länger. Dafür bekommst du keinen Registrierungskonflikt beim Start und sitzt nicht vor einem Stack-Trace mit dem Gesicht „ich wollte doch nur zwei ConsoleWriter…".

Beachte: Wir gehen jetzt nicht auf das Thema ein, wie man zwischen mehreren Implementierungen eines Interfaces wählt — das ist eine eigene Geschichte. Hier geht es nur darum, dass die Package-Struktur Konflikte bei Bean-Namen nicht verhindert, weil der Standard-Bean-Name nichts von Packages weiß.

Mini-Refactoring von Packages und Scanning-Prüfung

In der Praxis entsteht eine gute Struktur nicht dadurch, dass du dich hinsetzt und sofort eine perfekte Architektur entwirfst. Meistens schreibt man erst „wie es geht", und dann kommt ein kleines Refactoring: Klassen wandern zwischen Packages, und das Projekt wird lesbarer. Wichtig ist, das sorgfältig zu tun, denn Java hat eine strenge Verbindung „Dateipfad ↔ package am Anfang der Datei". Die gute Neuigkeit ist, dass IDEs das können, und uns ist vor allem wichtig, die Logik des Prozesses zu verstehen und keine Angst davor zu haben.

Stell dir vor, du hattest die Klasse ScenarioRunner im Package com.example.contextflow — direkt in der Wurzel — und willst sie nach com.example.contextflow.application.scenario verschieben. Die minimalen Code-Änderungen sehen so aus.

Erstens ändert sich die package-Zeile:

package com.example.contextflow.application.scenario;

import org.springframework.stereotype.Component;

@Component // Nach dem Verschieben zwischen Packages bleibt die Annotation, wichtig ist nur, dass das Package unter Scanning fällt
public class ScenarioRunner {

    public void run() {
        // Der Methoden-Body ist für Scanning unwichtig: wichtig ist, dass Spring diese Klasse überhaupt gefunden hat
        System.out.println("ContextFlow started"); // ContextFlow started
    }
}

Zweitens ändern sich die Imports dort, wo du ihn verwendest. Zum Beispiel in der Main-Klasse der Anwendung, die man als Einstiegspunkt des Projekts in der Wurzel des Packages lassen kann:

package com.example.contextflow;

import com.example.contextflow.application.scenario.ScenarioRunner;
import com.example.contextflow.config.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ContextFlowApp {

    public static void main(String[] args) {
        // Wir starten den Kontext aus der Konfiguration und holen den Runner-Bean aus dem Container
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            context.getBean(ScenarioRunner.class).run();
        }
    }
}

Und jetzt der wichtigste Test des Refactorings — nicht „ob es kompiliert". Der wichtigste Test ist: „Startet der Kontext und sieht er den Runner?" Denn die Package-Struktur bringen wir nicht der Schönheit wegen in Ordnung, sondern für ein vorhersagbares Scanning.

Wenn du nach dem Verschieben den Fehler „bean not found" siehst, sollte der erste Gedanke nicht „Spring ist kaputt" sein, sondern „liegt der Runner wirklich innerhalb von com.example.contextflow und seinen Unterpaketen?" und „schaut @ComponentScan wirklich dorthin, wo es soll?". In einer solchen Diagnose liegt eine angenehme Einfachheit: Du überprüfst zuerst die Karte, statt nach Bugs in der Business-Service-Logik zu suchen.

8. Typische Fehler in der Package-Struktur beim Scanning

Fehler Nr. 1: Wichtige Klassen landen außerhalb des Root-Package, das du scannst.
Das ist der häufigste Grund für die Frage „Warum sieht Spring mein @Service nicht?". Das passiert meist nach einem Refactoring: Du hast eine Klasse in ein neues Package verschoben, aber die Scanning-Grenze nicht neu überprüft. Am Ende scannt der Container ehrlich ein Baum, und du wartest darauf, dass er eine Klasse in einem anderen findet. Das heilet nicht Annotationen, sondern Disziplin: ein Root-Package für die Anwendung und alle Komponenten darin.

Fehler Nr. 2: Struktur „alles in einem Package" — Scanning funktioniert zwar, aber das Projekt wird unleserlich.
Dem Container ist es technisch egal, ob OrderPlacementService neben Order und ConsoleAuditWriter liegt. Aber dir — nicht. Nach einer Woche wirst du Zeit damit verbringen, Klassen zu suchen, und nach einem Monat anfangen, Code zu scheuen: „da hängt doch alles mit allem zusammen". Die Gewohnheit, domain, application und infrastructure zu trennen, bewahrt davor lange bevor echte Komplikationen entstehen.

Fehler Nr. 3: Zombie-Packages util/common/misc wachsen und werden zu einem schwarzen Loch.
Am Anfang fallen 2–3 „nützliche" Klassen hinein, dann 10, dann 30, und plötzlich wird util zum Haupt-Package des Projekts, das niemand versteht. Für Scanning ist das ebenfalls unangenehm: Du fängst an, „Utilities" als Komponenten zu annotieren, und der Container holt zufällige Dinge in den Kontext. Besser von Anfang an Packages nach Verantwortung benennen und util allenfalls als temporäre Quarantäne lassen — und auch nur kurz.

Fehler Nr. 4: Gleiche einfache Klassennamen in verschiedenen Packages führen zu Konflikten bei Bean-Namen.
Packages in Java lösen Klassennamenkonflikte, aber Spring verwendet beim Scanning standardmäßig den einfachen Klassennamen als Bean-Namen. Daher können zwei ConsoleWriter in verschiedenen Packages bei der Registrierung kollidieren. In einem echten Projekt löst sich das normalerweise durch vernünftige Klassennamen — zum Beispiel „AuditConsoleWriter" und „NotificationConsoleSender" — und wenn nötig durch explizite Benennung in @Component("..." ).

Fehler Nr. 5: Konfiguration und Business-Klassen sind so durchmischt, dass unklar ist, wo der „Zusammenbau-Punkt" liegt.
Wenn nebeneinander AppConfig, Order, OrderPlacementService und noch zehn Klassen liegen, verlierst du das Gefühl, wo überhaupt der Einstieg in die Anwendung ist. Selbst jetzt, wo unsere Konfiguration minimal ist, ist es nützlich, sie in einer eigenen Zone config zu halten, damit das Lesen des Projekts an einem verständlichen Ort beginnt: „hier ist der Einstiegspunkt, hier die Scanning-Grenzen, hier das Hauptszenario des Starts".

1
Aufgabe
Spring Core, Level 5, Lektion 3
Gesperrt
Trennung von Domain und Application
Trennung von Domain und Application
1
Aufgabe
Spring Core, Level 5, Lektion 3
Gesperrt
Paketstruktur f\u00fcr das Export-Szenario
Paketstruktur f\u00fcr das Export-Szenario
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION