CodeGym /Kursy /Spring Boot /Jak złożyć szablon Spring Boot

Jak złożyć szablon Spring Boot

Spring Boot
Poziom 28, Lekcja 1
Dostępny

1. Wielokrotnie używalny szablon Spring Boot

Gdy mówimy „szablon projektu", u początkujących często pojawia się obraz: „No, to jak taki pusty projekt, żeby szybciej zacząć". Ale dobry szablon Spring Boot to nie pustka, lecz minimum, które jest już uzgodnione: struktura pakietów, konfiguracja, diagnostyka, pakowanie i testy działają jako jedna całość. To jak walizka na podróż: nie trzeba brać ze sobą całego domu, ale zapomnieć paszportu też nie wchodzi w grę.

Najważniejszy wyznacznik szablonu brzmi przyziemnie: można go oddać innej osobie i ona odpali serwis (najlepiej – bez dzwonienia do ciebie o 23:40 z pytaniem „a gdzie tu zmienić port?"). I drugi wyznacznik – ty sam za miesiąc otworzysz projekt i zrozumiesz, co gdzie leży, bez urządzania wykopalisk archeologicznych.

Spójny obraz serwisu złożyliśmy już od góry do dołu. Teraz ważne jest wydzielenie z niego tego, co powinno być nudno-stabilne także w następnym serwisie Boot: szkielet projektu, reguły konfiguracji, diagnostyka, pakowanie i minimalne smoke-checks.

Żeby nie pozostawać w filozofii, zafixujmy „skład szablonu" w postaci kompaktowej tabeli. To nie jest check-lista na wszystkie przypadki życia, lecz właśnie minimalne „sklejenie" części, bez którego projekt wygląda jak fragmenty demo.

Część szablonu Po co potrzebna Jak objawia się w catalog-service
Struktura pakietów Żeby projekt czytał się „według mapy", a nie „według intuicji" config,
catalog
,
actuator
,
support
,
static
Konfiguracja YAML + profile Żeby zachowanie zmieniało się bez przekompilowania application.yaml +
application-local/dev/prod.yaml
@ConfigurationProperties Żeby ustawienia były typizowane i scentralizowane CatalogProperties jako jeden „centrum konfiguracji"
Diagnostyka Żeby serwis był wyjaśnialny „na starcie i w runtime" StartupSummaryRunner,
CatalogDataHealthIndicator
Pakowanie Żeby serwis żył poza IDE bootJar i uruchomienie
java -jar
Mini-testy Żeby szablon nie psuł się po cichu contextLoads, web smoke, health smoke, override-test
README + przykładowe żądania Żeby serwis można było „przekazać" i szybko sprawdzić README.md,
http/catalog-service.http

Dalej interesują nas właśnie te kawałki – nie jako zestaw nowych podsystemów, lecz jako te miejsca, które sprawiają, że serwis jest wielokrotnie używalny i przewidywalny.

2. Stabilny szkielet projektu

W szablonie szkielet nie powinien za każdym razem stać się osobną przygodą. Im mniej niespodzianek w punkcie wejścia i budowaniu, tym mniejsze szanse, że nowy serwis zacznie się od walki ze scanning, wersjami i przypadkowymi pluginami.

Dlatego baseline jest tu celowo nudny – i to dobrze: Gradle Wrapper, Boot-plugin, curated startery, korzeń CatalogServiceApplication z @SpringBootApplication i @ConfigurationPropertiesScan. Ta klasa w template to nie powód, żeby znowu robić pełny walkthrough, lecz punkt odniesienia: ustawia base package i wyraźnie trzyma typizowaną konfigurację w zasięgu widoczności.

Jeśli klasa main wyjedzie głęboko w podpakiet, a zależności zaczną się składać na zasadzie „znalazłem w artykule – dodałem", szablon przestaje być przewidywalny. Dobry szkielet jest właśnie tym użyteczny, że prawie nie chce się go ruszać bez realnego powodu.

3. Struktura pakietów

Struktura pakietów to coś, co prawie nigdy nie wydaje się ważne w chwili, gdy kod „już działa". A potem projekt urasta o 20 plików i nagle okazuje się, że nie pamiętasz, gdzie leży konfiguracja, gdzie warstwa web, gdzie serwisy, a gdzie „jakaś klasa, która z jakiegoś powodu wpływa na wszystko". Szablon powinien rozwiązywać ten ból z góry: stworzyć znajomą geografię, gdzie każde miejsce ma sens.

W catalog-service używamy hybrydy: package-by-feature dla domeny katalogu i osobne pakiety infrastrukturalne. To dobra strategia startowa dla juniorów: nie wchodzisz w skomplikowane szkoły architektoniczne, ale nie wrzucasz też wszystkiego do jednego worka. Najważniejsze – żeby role pakietów można było wyjaśnić jednym zdaniem.

Utrwalmy role pakietów właśnie jako część szablonu (nie jako „wariant", lecz jako konwencję projektu):

Pakiet Co tam mieszka Typowy przykład klasy
catalog.domain proste modele dziedzinowe CourseCard,
Money
,
CourseTrack
catalog.repository źródło danych (in-memory) InMemoryCourseCatalogRepository
catalog.service logika aplikacyjna CourseCatalogService
catalog.web wejście HTTP, kontrolery CourseCatalogController
catalog.bootstrap zachowanie startowe, seedery/runnery StartupSummaryRunner
config małe tematyczne konfiguracje WebConfiguration,
StartupConfiguration
actuator domenowo-osadzona diagnostyka CatalogDataHealthIndicator
support mała infrastrukturalna „pomoc techniczna" support.logging.* (jeśli jest)
static (resources) landing page i pliki statyczne static/index.html

Żeby to nie było abstrakcyjne, oto „szkielet" struktury, który dobrze działa właśnie jako szablon:

com.example.catalogservice
|-- CatalogServiceApplication
|-- config
|-- catalog
|  |-- domain
|  |-- repository
|  |-- service
|  |-- bootstrap
|  \-- web
|-- actuator
\-- support

Dlaczego to ważne właśnie dla szablonu? Bo szablon to nie „projekt jednorazowy", lecz gotowa baza. A gotowa baza bez struktury to jak pudełko LEGO bez posortowanych klocków: technicznie można złożyć, ale przyjemność szybko mija.

4. Role plików konfiguracyjnych

Gdy mówimy „struktura projektu", wielu myśli tylko o pakietach Java. Ale w Spring Boot połowa architektury mieszka w konfiguracji. I jeśli konfiguracja jest źle zorganizowana, szablon wychodzi „kulawy": możesz kopiować kod, ale za każdym razem będziesz tracić czas na domysły, który YAML za co odpowiada i dlaczego jedna wartość wygrywa z drugą. Dobry szablon traktuje konfigi jak kod: każdy plik ma swoją odpowiedzialność.

W catalog-service celowo trzymamy się YAML-first, a konfigurację rozkładamy na kilka poziomów: bazowa, profilowa, importowane dane i zewnętrzne overrides. To sprawia, że system jest przewidywalny. W szablonie powinieneś umieć pokazać nowicjuszowi i powiedzieć: „Tu – bazowe ustawienia, tu – różnice między środowiskami, tu – dane katalogu, tu – zewnętrzne uzupełnienie".

Wygodnie myśleć o konfigach nie jako „wiele plików", lecz jako „warstwy". Oto przybliżony zestaw, który już wygląda jak dojrzały szablon:

src/main/resources/
|-- application.yaml
|-- application-local.yaml
|-- application-dev.yaml
|-- application-prod.yaml
\-- catalog-data.yaml

Kluczowa idea – różne obowiązki. Tabelka pomaga się nie pogubić:

Plik Jego sens Przykładowa zawartość
application.yaml baza, wspólne ustawienia, importy spring.application.name, spring.config.import, wartości domyślne
application-local.yaml lokalne udogodnienia rozszerzony Actuator exposure, bardziej szczegółowe logi
application-dev.yaml środowisko dev kompromis między lokalnym komfortem a „prawie jak prod"
application-prod.yaml zachowanie produkcyjne minimalny exposure, bardziej rygorystyczne ustawienia
catalog-data.yaml dane katalogu (lista kursów) app.catalog.courses: ...

I oto minimalny, ale bardzo wymowny fragment bazowego application.yaml, który w szablonie powinien „wyjaśniać się sam":

spring:
  application:
    name: catalog-service # Nazwa aplikacji (używana w logach, metrykach itp.)
  config:
    import: "classpath:catalog-data.yaml" # Dane katalogu trzymamy w osobnym pliku

app:
  catalog:
    title: "Spring+ Catalog" # Tytuł do wyświetlania/landing
    maintenance-mode: false # Flaga zachowania: można szybko „wyłączyć" funkcjonalność
    max-featured-count: 4 # Limit wpływający na odpowiedź API
    default-published-only: true # Domyślne zachowanie przy filtrowaniu
    startup-report-enabled: true # Włączenie/wyłączenie raportu startowego

Ważna kwestia: nawet jeśli nie pamiętasz dokładnego priorytetu wszystkich źródeł właściwości, taka struktura konfiga już ratuje od chaosu. „Dane katalogu" leżą osobno – więc gdy będziesz chciał je zastąpić, wiesz, co ruszać. „Różnice profilowe" leżą osobno – więc gdy chcesz zmienić exposure Actuatora w local, nie grzebiesz w bazie i nie tworzysz przypadkowych rozwidleń.

5. Typizowana konfiguracja: CatalogProperties

Najszybszy sposób uczynić projekt niemożliwym do ponownego użycia – to rozmazać konfigurację po kodzie stringowymi kluczami. Dziś „i tak widać", a za dwa tygodnie nie jesteś pewien, czy klucz jest poprawny, gdzie jest używany i co się stanie, jeśli go przemianujesz. W szablonie typizowana konfiguracja to nie ozdoba, lecz kręgosłup: jedna klasa (lub zestaw records) sprawia, że ustawienia są widoczne, typizowane i scentralizowane.

W catalog-service takim centrum jest CatalogProperties. To właśnie ta rzecz zamienia YAML z „tekstu z boku" w część architektury aplikacji. I to bardzo szablonowe rozwiązanie: kopiujesz projekt – i od razu dostajesz poprawny styl konfiguracji, a nie „każdy pisze jak przywykł".

Oto kompaktowa forma CatalogProperties (można rozszerzać, ale ważne, żeby zachować ideę):

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@ConfigurationProperties("app.catalog") // Powiązanie z prefixem w YAML: app.catalog.*
public record CatalogProperties(
        String title, // Czytelna dla człowieka nazwa katalogu
        boolean maintenanceMode, // Flaga „jesteśmy na obsłudze technicznej"
        int maxFeaturedCount, // Limit dla wyświetlania featured
        boolean defaultPublishedOnly, // Domyślne filtrowanie opublikowanych
        boolean startupReportEnabled, // Włączamy/wyłączamy StartupSummaryRunner
        List<CourseProperties> courses // Dane katalogu importowane z osobnego YAML
) {
}

Zwróć uwagę na kompozycję: mamy zarówno „flagi zachowania" (maintenanceMode, startupReportEnabled), jak i „limity" (maxFeaturedCount), i dane (courses). To właśnie to sprawia, że szablon jest użyteczny: nie tylko konfigurujesz, ale i widzisz, czym serwis jest zarządzany z zewnątrz.

Żeby szablon był odczuwalnie spójny, CatalogProperties powinien być realnie używany w logice, a nie leżeć „dla galerii". Na przykład limit kursów featured to nie stała w serwisie, lecz ustawienie:

import java.util.List;

public List<CourseCard> findFeatured() {
    int limit = properties.maxFeaturedCount();
    return repository.findFeatured(limit);
}

Jest tu prosta „architektoniczna dyscyplina" szablonu: jeśli wartość wpływa na zachowanie serwisu i potencjalnie może się zmieniać między środowiskami lub przy uruchomieniu jako jar – to kandydat do konfiguracji, a nie do literału.

I jeszcze jeden subtelny punkt, który czyni szablon dojrzałym: zachowanie włącza się/wyłącza właściwościami, a nie komentowaniem kodu. Na przykład raport startowy można robić warunkowym. Demonstruje to „property-driven behavior" na zrozumiałym poziomie.

Właśnie taki snapshot dla szablonu uznajemy za finalny: startup summary włącza się przez rejestrację beana na podstawie właściwości, a nie żyje jako bezwarunkowy @Component.

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // Konfiguracja wydzielona osobno: wygodne do przenoszenia do innych serwisów
public class StartupConfiguration {

    @Bean // Rejestrujemy runner jako bean, żeby działał przy starcie aplikacji
    @ConditionalOnProperty(
            prefix = "app.catalog",
            name = "startup-report-enabled",
            havingValue = "true",
            matchIfMissing = true // Jeśli właściwości nie ma – uznajemy, że raport jest włączony
    )
    public StartupSummaryRunner startupSummaryRunner(CatalogProperties properties) {
        // Przekazujemy typizowane właściwości do komponentu startowego
        return new StartupSummaryRunner(properties);
    }
}

Tak, to tylko jeden @ConditionalOnProperty, ale jako element szablonu sprawia, że projekt jest „konfigurowalny bez poprawek". A to oznacza – naprawdę wielokrotnie używalny.

6. Diagnostyka domyślna

Normalny szablon projektu dba nie tylko o to, żeby serwis „działał", ale i o to, żeby można go było wyjaśnić: co załadował, w jakim trybie wystartował, dlaczego tak się zachowuje, czy w ogóle żyje. W projekcie uczelnianym można przymknąć oko i powiedzieć „no przecież u mnie na localhost otwiera się". W szablonie to niebezpieczny nawyk: jak tylko serwis wychodzi ze świata IDE, potrzebna jest obserwowalność bez szamaństwa.

W catalog-service diagnostyka to połączenie „małych, ale użytecznych" elementów: StartupSummaryRunner (logika startu), Actuator (endpointy widoczności) i niestandardowy HealthIndicator (domenowo-osadzony health). Idea szablonu: nie zamieniać tego w infrastrukturalnego potwora, ale dać minimum, które realnie pomaga.

Same komponenty diagnostyczne pozostają przy tym małe i nudne: runner to zwykła klasa, health to zwykły HealthIndicator. Ważne, żeby nie mylić tego z rejestracją: decyzję o podniesieniu StartupSummaryRunner podejmuje config z poprzedniego rozdziału.

Przykład raportu startowego może być prosty: jedna linia w logu to już sygnał, że CatalogProperties się powiązały i serwis wystartował w oczekiwanym trybie.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;

public class StartupSummaryRunner implements ApplicationRunner {

    private static final Logger log = LoggerFactory.getLogger(StartupSummaryRunner.class);

    // Trzymamy konfigurację jako zależność: to właśnie jest styl „property-driven"
    private final CatalogProperties properties;

    public StartupSummaryRunner(CatalogProperties properties) {
        this.properties = properties;
    }

    @Override
    public void run(ApplicationArguments args) {
        // Minimalna diagnostyka: po logu widać, że bean został stworzony i właściwości się powiązały
        log.info("Catalog title: {}", properties.title());
    }
}

A teraz – ważny „szablonowy" kawałek: health. W myśleniu produkcyjnym health to nie tylko „serwer odpowiada". Powinien odpowiadać „serwis jest w dobrym stanie". Dla naszego katalogu oznacza to przynajmniej obecność opublikowanych kursów (bo inaczej nie ma co pokazywać użytkownikowi).

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component // Podłączamy do kontekstu automatycznie, bez ręcznej rejestracji
public class CatalogDataHealthIndicator implements HealthIndicator {

    // Health zależy od logiki domenowej: dzięki temu staje się „sensowny", a nie formalny
    private final CourseCatalogService service;

    public CatalogDataHealthIndicator(CourseCatalogService service) {
        this.service = service;
    }

    @Override
    public Health health() {
        // Minimalne kryterium „zdrowia": w katalogu jest co pokazywać użytkownikowi
        return service.countPublished() > 0
                ? Health.up().build()
                : Health.down().withDetail("reason", "no published courses").build();
    }
}

Dla szablonu ważne jest właśnie to, że to jest mały i tematyczny komponent. Nie wciąga do projektu platform infrastrukturalnych, ale daje to, co w prawdziwym życiu oszczędza godziny: „dlaczego wszystko puste?" – „bo published=0, oto health".

7. Minimalne smoke-testy

Szablon ma jeden nieprzyjemny tryb awarii: ktoś go skopiował, trochę zmienił, i po tygodniu nikt nie pamięta, kiedy dokładnie zepsuł się wiring czy config. Dlatego template nie potrzebuje całego testing-landscape, lecz małego zabezpieczającego zestawu, który łapie najczęstsze regresje.

Dla catalog-service wystarczą cztery sprawdzenia:

  • contextLoads() – kontekst w ogóle się podnosi;
  • test override właściwości – CatalogProperties naprawdę reaguje na nadpisanie właściwości;
  • web smoke – bazowy endpoint odpowiada;
  • health smoke – /actuator/health jest dostępny.

Nazwy tych testów są tu ważniejsze niż pełny replay kodu. Jeśli taki zestaw istnieje, szablon nie psuje się po cichu po skopiowaniu i małych poprawkach. Potrzebujemy tu właśnie zabezpieczającego baseline, bez zamieniania template w poligon dla architektury testowej.

8. Pliki towarzyszące szablonu

Jest nieprzyjemna prawda z prawdziwego programowania: projekt bez README to projekt, który „działa tylko u autora". I tak, autorem często jesteś ty. A szablon powinien żyć dłużej niż jedna twoja sesja w IDE. Dlatego pliki towarzyszące są częścią szablonu na równi z kodem. Tu bez patosu: po prostu żeby ktoś mógł uruchomić serwis i ręcznie sprawdzić główne endpointy.

Pliki towarzyszące dobrego szablonu zwykle składają się z dwóch rzeczy: krótki README.md i zestaw przykładowych żądań. README nie musi być powieścią – to „instrukcja obsługi" i „mapa wejścia". Przykładowe żądania to szybki sposób na sprawdzenie projektu bez rytuałów z Postmanem.

Oto przykładowy fragment README, który w szablonie naprawdę oszczędza czas:

## Uruchomienie

Lokalnie (profil `local`):
./gradlew bootRun --args='--spring.profiles.active=local'

Budowanie i uruchomienie jako jar:
./gradlew bootJar
java -jar build/libs/catalog-service.jar --spring.profiles.active=local

I przykład http/catalog-service.http (lub jakikolwiek odpowiednik), który daje „sprawdzenie w minutę":

### Courses (default)
GET http://localhost:8080/api/catalog/courses

### Featured
GET http://localhost:8080/api/catalog/featured

### Health
GET http://localhost:8080/actuator/health

Dlaczego to ważne właśnie dla szablonu? Bo szablon jest prawie zawsze używany tak: „stworzono nowy serwis → skopiowano szablon → wprowadzono małe zmiany → sprawdzono, że nic bazowego nie wypadło". I te dwa pliki zamieniają „sprawdzono" z półtorej godziny w dziesięć minut.

9. Typowe błędy przy zamienianiu serwisu w szablon

Błąd nr 1: próba stworzenia „uniwersalnego szablonu na wszystkie projekty świata".
Gdy próbujesz przykryć szablonem wszystkie możliwe przyszłe potrzeby, przestaje być mały i zrozumiały. W efekcie dostajesz nie szablon, lecz mini-framework własnej produkcji, którego nikt nie odważy się ruszyć. Dobry szablon uczciwie pokrywa bazę i zostawia miejsce na wzrost.

Błąd nr 2: trzymanie „ważnych ustawień" w kodzie, bo „tak szybciej".
Limity, flagi, tryby i nazwy, które potencjalnie będziesz chciał zmieniać bez przebudowy, powinny żyć w YAML i wiązać się przez @ConfigurationProperties. Gdy zostają w literałach, szablon traci główną zaletę Boot: konfiguracyjne zarządzanie zachowaniem.

Błąd nr 3: mieszanie obowiązków plików konfig, dopóki „i tak działa".
Jeśli application.yaml zamienia się w śmietnik danych, profile zaczynają duplikować połowę ustawień, a importowane pliki nie mają jasnej roli, projekt szybko staje się niezarządzalny. Szablon wygrywa nie liczbą konfigów, lecz tym, że każdy z nich odpowiada za swoją warstwę.

Błąd nr 4: robienie diagnostyki opcjonalną i zapominanie o niej.
Logi startowe, StartupSummaryRunner, Actuator i przynajmniej jeden domenowo-sensowny health-indicator to nie „do produkcji na później". To to, co sprawia, że serwis jest wyjaśnialny już teraz. Szablon bez diagnostyki zwykle żyje dokładnie do pierwszego „dlaczego u mnie puste, ale bez błędów".

Błąd nr 5: uważanie, że „kontekst podniósł się w IDE" to jest sprawdzenie szablonu.
Wielokrotna używalność sprawdza się nie IDE-szczęściem, lecz powtarzalnością: bootRun, bootJar i java -jar, plus kilka minimalnych smoke-testów. Jeśli tego nie ma, szablon zamienia się w „demo, które czasem się odpala".

1
Zadanie
Spring Boot, poziom 28, lekcja 1
Niedostępne
Mini-szablon z przejrzystą strukturą pakietów
Mini-szablon z przejrzystą strukturą pakietów
1
Zadanie
Spring Boot, poziom 28, lekcja 1
Niedostępne
Szablon z modułową konfiguracją YAML
Szablon z modułową konfiguracją YAML
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION