CodeGym /Kursy /Spring Boot /Pakiety catalog-service

Pakiety catalog-service

Spring Boot
Poziom 7, Lekcja 1
Dostępny

1. Pakiety jako „mapa terenu" projektu

Pakiety w Javie często postrzegane są jako coś kosmetycznego: „no to po prostu foldery, żeby pliki się nie mieszały". Ale w prawdziwym projekcie backendowym pakiet to prawie jak ulica i numer domu: po adresie rozumiesz, gdzie mieszka kod i po co ci go potrzeba. Dobra struktura pomaga się orientować bez czytania każdego pliku.

Wyobraź sobie mieszkanie-studio, w którym jednocześnie jest kuchnia, sypialnia, spiżarnia, biuro i siłownia. Technicznie można żyć. Ale szukać skarpetek wśród garnków i hantli – to wątpliwa przyjemność. Płaski projekt Java „w jednym pakiecie" to mniej więcej to samo: klasy fizycznie istnieją, ale nawigacja i rozumienie ról zaczyna się sypać już przy 15–20 plikach.

W Spring Boot to ważniejsze niż „po prostu w Javie", bo pakiety wpływają nie tylko na wygodę czytania, ale i na to, co kontener w ogóle zobaczy. Czyli struktura to nie tylko estetyka, ale część mechaniki aplikacji. A poza tym to dyscyplina dla mózgu: gdy wybierasz pakiet, jesteś zmuszony odpowiedzieć sobie na proste pytanie: „Ten kod jest o czym? O domenie? O infrastrukturze? O diagnostyce?"

I tu pojawia się kluczowa myśl dnia: pakiety powinny pomagać objaśnić projekt osobie, która otworzyła repozytorium po raz pierwszy. Najlepiej, żeby tą osobą mógł być ty sam za dwa tygodnie – bo pamięć programisty jak przeglądarka: cache się przeterminowuje, a bugi zostają.

2. Główny pakiet i component scanning

Teraz będzie ważny moment, który niszczy nawyk „no, main-klasę wrzucę gdziekolwiek, ona przecież tylko uruchamia". W Spring Boot klasa main to nie tylko public static void main(...). Wyznacza punkt składania aplikacji, bo @SpringBootApplication włącza component scanning, a skanowanie zaczyna się od pakietu, w którym leży ta klasa.

Mówiąc prościej, Boot patrzy na pakiet com.example.catalogservice i mówi: „Okej, będę szukać komponentów wewnątrz tego pakietu i we wszystkich podpakietach". Dlatego wygodnie trzymać klasę główną jak najwyżej – w pakiecie głównym aplikacji, żeby „widziała" wszystko, czego potrzeba.

Oto dobry, „domyślnie prawidłowy" wariant:

package com.example.catalogservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// Główny punkt wejścia: od pakietu tej klasy Spring Boot startuje component scanning
@SpringBootApplication
public class CatalogServiceApplication {

    public static void main(String[] args) {
        // Uruchamiamy kontekst Spring i cały cykl życia aplikacji
        SpringApplication.run(CatalogServiceApplication.class, args);
    }
}

I oto typowa pułapka nowicjusza: wrzucasz klasę main np. do com.example.catalogservice.config, bo „to przecież konfiguracja aplikacji". A potem nagle okazuje się, że com.example.catalogservice.catalog.* już nie jest skanowane, i twoje @Service/@Repository po prostu się nie tworzą. I zaczynasz podejrzewać spisek, merkury wsteczny i Kotlin – choć przyczyna jest banalna: sam obciąłeś zakres skanowania.

Bardzo wygodnie trzymać w głowie taki obrazek (nie o wnętrznościach Springa, a o zdrowym rozsądku):

flowchart TD
    A["com.example.catalogservice CatalogServiceApplication"] --> B["config"]
    A --> C["catalog"]
    A --> D["support"]
    A --> E["actuator"]
    C --> C1["domain"]
    C --> C2["repository"]
    C --> C3["service"]
    C --> C4["web"]
    C --> C5["bootstrap"]

Skanowanie idzie „w dół drzewa". Dlatego drzewo trzeba budować tak, żeby korzeń był tam, gdzie spodziewasz się „zobaczyć cały projekt".

3. Hybryda feature i infrastructure

Gdy po raz pierwszy próbujesz „zrobić ładnie", jest pokusa pójścia w skrajność. Jedni robią strukturę „po warstwach" (controller/service/repository), inni – „po ficzerach" (catalog, orders, users), a jeszcze inni robią trzecią skrajność – pakiet util, do którego wrzucają wszystko, co nie znalazło domu. W naszym kursie wybieramy hybrydowy, bardzo praktyczny wariant.

Podejście „po warstwach" (web, service, repository) jest dobre, gdy projekt jest zupełnie mały i masz jedną domenę. Ale gdy pojawia się druga ficzerka, dostajesz długą listę kontrolerów w jednym pakiecie i długą listę serwisów w innym, i już po strukturze nie wiadomo, które klasy są ze sobą powiązane.

Podejście „po ficzerach" (każda ficzerka żyje osobnym blokiem i ma wewnątrz swoje web/service/repository) świetnie się skaluje, ale dla projektu edukacyjnego na początku może wyglądać jak przesadna komplikacja: „po co modularność, jeśli mam jedną domenę?"

Dlatego dla catalog-service robimy tak: jedna główna strefa feature – pakiet catalog, a obok niego – pakiety infrastrukturalne, które nie należą do konkretnej ficzerki biznesowej, ale należą do aplikacji jako platformy: config, support, actuator. Tak projekt czyta się bardzo naturalnie: „oto domena, a oto obwódka wokół niej".

I tak, to też pedagogicznie uczciwe. Nie udajemy, że budujemy gigantyczny enterprise, ale też nie robimy „kupki klas w jednym miejscu". Budujemy strukturę, która wytrzyma rozrost kursu, a nie posypie się w Dniu 10, gdy pojawi się warstwa web.

4. Struktura catalog-service

Żeby struktura nie była „abstrakcyjną ideą", trzeba ją ustalić jako oczekiwaną mapę projektu. W tym kursie mapa nie musi być idealna na wieki, ale powinna być wystarczająco stabilna, żebyś się po niej orientował do końca nauki. Dlatego z góry ustalamy docelowe drzewo pakietów i stopniowo zapełniamy je klasami.

Oto ten sam „szkielet", do którego dążymy (pakiety Java i klasy):

com.example.catalogservice
├── CatalogServiceApplication
├── config/
├── catalog/
│   ├── domain/
│   ├── repository/
│   ├── service/
│   ├── bootstrap/
│   └── web/
├── actuator/
└── support/

Pakiet catalog.web zakładamy już teraz jako miejsce dla wejściowego adaptera web. To jeszcze nie rozmowa o mechanice MVC, a po prostu granica strukturalna: kontrolery potem nie wylądują „gdzie popadnie".

I osobno ważne do zapamiętania: w projekcie jest nie tylko src/main/java, ale i src/main/resources. W resources są nie pakiety Java, lecz pliki zasobów: konfiguracja, strony statyczne, obrazki itd. Gdy zobaczysz src/main/resources/static, to nie będzie „pakiet static", lecz folder dla statycznych zasobów webowych. Na razie tam nie zaglądamy, ale już teraz warto nie mylić tych dwóch światów.

Żeby utrwalić „kto za co odpowiada", wygodnie mieć małą tabelkę. Nie zastępuje czytania kodu, ale pomaga szybko przypomnieć sobie rolę każdego bloku:

Pakiet To jest o… Przykłady tego, co tam żyje
catalog domenie „katalogu kursów" modele, repozytorium, serwisy, kod bootstrap katalogu
config jawnej konfiguracji Java aplikacji klasy @Configuration, metody @Bean
support małym infrastrukturalnym pomocnikom normalizatory, małe narzędzia z jasną rolą
actuator diagnostyce i logice health-check health indicatory i inne rzeczy diagnostyczne
resources/static statycznym zasobom dla web index.html i wszystko, co jest serwowane jako pliki

Chodzi o to, żebyś otworzył projekt i od razu zrozumiał: „Ok, logika biznesowa tu, konfiguracja tam, a reszta – strefy pomocnicze".

5. Strefa feature catalog

Pakiet catalog to nasz „świat domeny". Nie musi być idealnym rajem DDD, ale musi być zrozumiały: tu leży wszystko, co dotyczy katalogu kursów, a nie tego, jak dokładnie Spring Boot to uruchamia. Celowo trzymamy catalog jako osobny blok, żeby nie mieszać domeny i infrastruktury.

Wewnątrz catalog z góry dzielimy kod po rolach, bo role klas są różne. Modele (domain) opisują dane i pojęcia. Repozytorium (repository) odpowiada za dostęp do danych (w naszym projekcie – in-memory, bez bazy). Serwis (service) trzyma działania aplikacyjne i reguły. Bootstrap (bootstrap) – to startowe rzeczy związane z katalogiem (inicjalizacja, seeding, raport ze startu). Web (web) – to miejsce dla wejściowego adaptera web, żeby kontrolery żyły blisko katalogu, a nie pojawiały się w przypadkowych pakietach.

Zobaczmy, jak wygląda „normalna" klasa domenowa w odpowiednim pakiecie. Może w ogóle nie wiedzieć o Springu – i to plus, a nie minus:

package com.example.catalogservice.catalog.domain;

public class CourseCard {

    // Identyfikator kursu w URL/ścieżkach (na razie tylko demonstracja pola domenowego)
    private final String slug;

    public CourseCard(String slug) {
        // W modelu domenowym można trzymać niezmienniki/sprawdzenia później, teraz ustalamy strukturę
        this.slug = slug;
    }
}

Tu ważna nie jest „ubogość modelu" (rozszerzymy ją później), ale to, że po ścieżce pliku i po pakiecie widać: to jest model domenowy katalogu, a nie konfiguracja ani kontroler.

Teraz przykład serwisu. On już jest komponentem Spring, więc ma @Service, i jak umówiliśmy się w poprzednim wykładzie, zależności przychodzą przez konstruktor:

package com.example.catalogservice.catalog.service;

import com.example.catalogservice.catalog.repository.CourseCatalogRepository;
import org.springframework.stereotype.Service;

@Service
public class CourseCatalogService {

    // Serwis opiera się na repozytorium do dostępu do danych katalogu
    private final CourseCatalogRepository repository;

    public CourseCatalogService(CourseCatalogRepository repository) {
        // Constructor injection: zależność jest obowiązkowa i widoczna jawnie
        this.repository = repository;
    }
}

Zauważ, jak czyta się struktura: catalog.service zależy od catalog.repository. Nawet nie znając szczegółów, możesz zgadnąć, że serwis będzie pobierał kursy z repozytorium i wykonywał działania aplikacyjne.

I minimalny przykład interfejsu repozytorium – też nie dla logiki, a dla demonstracji miejsca w strukturze:

package com.example.catalogservice.catalog.repository;

import com.example.catalogservice.catalog.domain.CourseCard;
import java.util.List;

public interface CourseCatalogRepository {

    // Kontrakt repozytorium: zwracamy wszystkie karty kursów (implementacja pojawi się później)
    List<CourseCard> findAll();
}

Na razie nie omawiamy pełnej warstwy web i kontraktu API, więc catalog.web może być pusty, i to jest normalne. Ważne, żeby miejsce na wejściowy adapter już było i żeby kontrolery potem nie lądowały „gdzie się da". Nawyk „gdzie się da" – to główny dostawca przyszłych refaktoryzacji.

6. Pakiety infrastrukturalne

Teraz spojrzyjmy na to, co leży obok catalog w korzeniu aplikacji. To strefy infrastrukturalne. Odpowiadają nie za to, „jakie mamy kursy", lecz za to, „jak zbudowana jest aplikacja", „jak startuje", „jak ją rozszerzać i diagnozować". Wygodnie trzymać je osobno, żeby domena nie utonęła w obwódce.

Pakiet config – to miejsce dla konfiguracji Java. Wrzucamy tam klasy z @Configuration, żeby nie zamieniać projektu w wysypisko adnotacji po całym kodzie. I tak, konfiguracja może być pusta na starcie, ale już sam fakt, że żyje w config, dyscyplinuje.

Minimalny przykład klasy konfiguracyjnej (teraz nic nie robi, ale pokazuje miejsce i rolę):

package com.example.catalogservice.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class StartupConfiguration {
    // Tu później pojawią się metody @Bean i ustawienia wiring, jeśli będą potrzebne
}

Pakiet support – to jednocześnie bardzo użyteczna i bardzo niebezpieczna strefa. Użyteczna, bo można tam wrzucać małe techniczne pomocniki, które nie należą do jednej konkretnej ficzerki. Niebezpieczna, bo bardzo łatwo wrzucić tam wszystko po kolei i dostać ten sam util, tylko pod inną nazwą. Dlatego zasada jest prosta: support istnieje tylko wtedy, gdy możesz wyjaśnić rolę każdej klasy ludzkimi słowami.

Na przykład taki mały pomocnik ma sens wrzucić do support, bo to nie logika biznesowa katalogu, lecz techniczna normalizacja ciągu:

package com.example.catalogservice.support;

public class CatalogSlugNormalizer {

    public String normalize(String slug) {
        // Najprostsza normalizacja: obcinamy spacje z brzegów i zamieniamy na małe litery
        return slug.trim().toLowerCase();
    }
}

Pakiet actuator zakładamy z góry, żeby potem nie „szukać domu" dla kodu diagnostycznego. Tam będą żyć rzeczy związane z obserwowalnością aplikacji (health, diagnostyka). Na razie może być pusty – to normalne.

I na koniec osobna myśl o static. W Spring Boot static to folder w resources, zazwyczaj src/main/resources/static. Przeznaczony jest dla plików statycznych, które Boot może serwować jako zasoby webowe. To nie jest pakiet Java. Dlatego nie twórz com.example.catalogservice.static i nie wrzucaj tam klas – wyglądałoby to jak pomylenie szafy z lodówką. I oboje potem dziwnie pachnąc.

7. Nazwy pakietów bez wstydu

Nazwy pakietów to małe decyzje, które sumują się w duży efekt. Jeśli pakiet jest dobrze nazwany, wyjaśnia rolę. Jeśli jest źle nazwany, maskuje problem: nie wiesz, gdzie kłaść kod, i dlatego nazywasz pakiet „jak leci". Najczęstsze „jak leci" to util, common, misc, stuff, helpers. To nie są nazwy, to przyznanie się do zagubienia.

Wyobraź sobie, że w sklepie powiedziano ci: „Twoje zamówienie leży w dziale »Różne«". Idziesz do „Różnych" – a tam baterie, zasłony, karma dla kotów i zestaw do haftu. Mniej więcej tak wygląda pakiet util: z początku wydaje się wygodny, potem nie można nic znaleźć, a co gorsza – nie można zrozumieć, co można tam dodawać, a czego nie.

Lepiej nazywać pakiet po sensie odpowiedzialności. Jeśli klasa normalizuje slug – to support albo nawet support.text (jeśli naprawdę potrzebna będzie podgrupa). Jeśli klasa dotyczy konfiguracji startu – config. Jeśli klasa jest o katalogu kursów – catalog.*.

Mała tabelka „zła nazwa → uczciwsza alternatywa" zazwyczaj szybko leczy oko:

Zła nazwa pakietu Dlaczego zła Zdrowsza alternatywa
util „wszystko po kolei", brak granicy support, support.text, support.logging (jeśli rola jest jasna)
common ogólny śmietnik bez właściciela konkretny owner: catalog, config, actuator
misc „miscellaneous" = „nie chce mi się myśleć" nazwać po odpowiedzialności klasy
services w korzeniu miesza ficzerki w jedną kupę catalog.service (powiązanie z ficzerką)
models w korzeniu nie wiadomo, modele czego catalog.domain

Jeszcze jeden praktyczny niuans: nie twórz głębokości pakietów dla samej głębokości. Pakiet com.example.catalogservice.catalog.domain.money.currency wygląda jak budowanie miasta, do którego najbliższego sklepu jedzie się autobusem. Niech najpierw struktura będzie płaska i zrozumiała, a skomplikowanie pojawia się tylko wtedy, gdy naprawdę pojawiają się dwie-trzy klasy jednego tematu.

8. Refaktoryzacja pakietów bez bólu

Przenoszenie klas między pakietami brzmi straszniej niż jest. Najbardziej nieprzyjemne w tym procesie – nie logika, a staranność: trzeba, żeby package ...; w pliku zgadzało się z realną ścieżką, żeby importów nie zostało „popsutych", i żeby Spring nadal widział komponenty przez component scan. Działając spokojnie, wszystko przebiega dość mechanicznie.

Zazwyczaj myślę tak: najpierw ustaliam pakiet główny, gdzie mieszka CatalogServiceApplication (zostaje w com.example.catalogservice). Potem tworzę pakiety drugiego poziomu: catalog, config, support, actuator. Następnie wewnątrz catalog tworzę podpakiety domain, repository, service, bootstrap, web. I dopiero potem zaczynam rozkładać klasy na miejsca – nie „jak popadnie", ale po roli.

Przy przenoszeniu klas lepiej korzystać z refaktoryzacji IDE (np. w IntelliJ IDEA to „Refactor → Move"), bo IDE jednocześnie poprawia wiersz package i aktualizuje importy w pozostałych plikach. Jeśli robić przenoszenie ręcznie przez system plików, prawie na pewno złapiesz 10 minut polowania na czerwone podkreślenia, i w pewnym momencie zaczniesz nienawidzić ludzkości. A IDE, w odróżnieniu od ludzkości, zazwyczaj działa przewidywalnie.

I jeszcze jeden ważny moment: jeśli widzisz, że nie wiesz, gdzie wrzucić klasę, to nie jest powód do tworzenia misc. To powód do uczciwego zapytania: „jaką rolę pełni ta klasa?" Czasem odpowiedź będzie taka: „klasa jest zbyt rozmyta i robi wszystko naraz". I to jest już sygnał do refaktoryzacji projektu, a nie wymyślania nowego pakietu-śmietnika.

9. Typowe błędy przy projektowaniu struktury pakietów

Struktura pakietów wydaje się nudnym tematem dokładnie do momentu, gdy otworzysz projekt, gdzie wszystko leży „pomieszane". Wtedy nagle rozumiesz, że to nie nuda, a kwestia przeżycia. Poniżej – typowe błędy, które najczęściej popełniają początkujący i dlaczego naprawdę utrudniają życie.

Błąd nr 1: „Jeden duży pakiet, potem ogarnę".
Z początku wydaje się, że tak szybciej: wszystko w com.example.catalogservice, a tam zobaczymy. Ale „zobaczymy" zazwyczaj nie nastaje, i po tygodniu nie pamiętasz, która klasa za co odpowiada. Leczy się po prostu: wydzielaj przynajmniej catalog i config od razu, a podpakiety dodawaj w miarę pojawiania się ról.

Błąd nr 2: klasa main w zbyt głębokim pakiecie.
Jeśli CatalogServiceApplication trafił do config albo do catalog, component scan zaczyna widzieć tylko część projektu. Efekt – zagadkowe błędy „bean not found", choć klasa niby jest oznaczona @Service. Trzymaj klasę main w pakiecie głównym aplikacji, a wszystkie pozostałe pakiety rób jej podpakietami.

Błąd nr 3: pakiet util jako „szuflada do wszystkiego".
util nie ma granic: można tam wrzucić wszystko, i dlatego wrzuca się wszystko. Potem nikt nie wie, czy można stąd zależeć i co w ogóle „util" oznacza. Jeśli już potrzebna jest wspólna warstwa, rób ją małą i uczciwą: support, i wewnątrz – tylko klasy, których rolę możesz wyjaśnić jednym zdaniem.

Błąd nr 4: mieszanie domeny i konfiguracji.
Gdy modele domenowe leżą obok klas @Configuration, projekt przestaje być czytelny. Czytelnik nie rozumie, gdzie jest sens biznesowy, a gdzie infrastruktura. Dobra struktura celowo rozdziela te rzeczy: catalog.* – o katalogu, config.* – o wiring/ustawieniach.

Błąd nr 5: pomylenie pakietów Java i resources/static.
Nowicjusze czasem tworzą pakiet Java static i wrzucają tam klasy, bo „widziałem static w projekcie". Ale static w Boot to folder zasobów, a nie kod Java. Umów się ze sobą: Java żyje w src/main/java, pliki statyczne – w src/main/resources/static, i to różne wszechświaty.

1
Zadanie
Spring Boot, poziom 7, lekcja 1
Niedostępne
Feature i pakiety infrastruktury dla witryny ksiazkowej
Feature i pakiety infrastruktury dla witryny ksiazkowej
1
Zadanie
Spring Boot, poziom 7, lekcja 1
Niedostępne
Struktura pakietów dla bloku harmonogramu
Struktura pakietów dla bloku harmonogramu
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION