1. Bazowa ścieżka: nie kosmetyka
Bazowa ścieżka wyznacza wspólny kontekst dla wszystkich publicznych adresów API. To jak nazwa miasta i dzielnicy na adresie: klient od razu widzi, że ma przed sobą właśnie publiczne API, a nie przypadkową stronę, nie panel admina ani nic wewnętrznego. I tak, to ten przypadek, gdy jeden dodatkowy segment oszczędza dziesiątki zbędnych pytań.
Bazowa ścieżka (base path) — to wspólny prefiks, od którego zaczynają się wszystkie publiczne adresy API. W naszym kursie utrwalamy go jako:
/api/v1
Ważne, że „bazowa ścieżka" — to niekoniecznie osobna techniczna encja w kodzie. Przede wszystkim jest to umowa co do formy adresów, która czyni API spójnym.
Wyobraź sobie, że otwierasz cudzy projekt i widzisz takie ścieżki:
- /tasks
- /api/tasks
- /api/v1/tags
- /v1/comments
Nawet jeśli każda z nich „działa", od razu masz wrażenie, że projekt sklejano z różnych kawałków różne osoby w różnym czasie. Klientowi też będzie nieprzyjemnie: będzie musiał zapamiętywać wyjątki. A wyjątki w API — jak wyjątki w matematyce: „tu pamiętamy, tu nie pamiętamy" szybko kończy się łzami.
Z bazową ścieżką wszystko staje się prostsze: z góry decydujesz, że publiczne API żyje „pod parasolem" jednego prefiksu, a dalej narastasz drzewo zasobów już pod nim.
Mini-schemat, żeby zobaczyć ideę w formie „konstruktora":
flowchart TD
BASE["/api/v1"] --> TASKS["/tasks"]
BASE --> TAGS["/tags"]
TASKS --> TASK_ID["/{taskId}"]
TASK_ID --> COMMENTS["/comments"]
Tu ważna nie jest konkretność tasks/comments/tags, lecz sam princip: jeden korzeń, dalej — sensowne segmenty zasobów.
2. Segment /api: granica publicznego API
Segment /api — to granica publicznego API, a nie dekoracyjna przystawka. Czasem początkującym wydaje się, że /api — to „po prostu tak przyjęte". Tak naprawdę to bardzo praktyczny marker. Sprawia, że aplikacja jest bardziej zrozumiała zarówno z zewnątrz, jak i od wewnątrz: od razu widać, gdzie zaczyna się publiczny interfejs dla klientów. To jak tabliczka „wejście dla gości" na budynku: pracownicy mogą chodzić przez wejście służbowe, ale gościowi potrzebne jest jedno zrozumiałe.
Segment /api rozwiązuje od razu kilka codziennych problemów, nawet jeśli na razie nie mówimy ani o frontendzie, ani o proxy, ani o infrastrukturze.
Po pierwsze, /api oddziela API od innych ścieżek, które mogą pojawić się w aplikacji. Nawet w projekcie edukacyjnym prędzej czy później pojawiają się pomocnicze rzeczy: dokumentacja, strony statyczne, jakieś wewnętrzne służbowe endpointy. Celowo nie rozwijamy w tym kursie „całego zoo" takich ścieżek, ale zdrowy rozsądek podpowiada: publiczne JSON API lepiej od razu wynieść do osobnej strefy.
Po drugie, /api pomaga człowiekowi czytać mapę aplikacji. Patrz, jak różnie są odbierane te dwa adresy:
- /tasks
- /api/v1/tasks
Pierwszy wygląda jak „strona serwisu" (w świecie webowym to bardzo częste skojarzenie). Drugi od razu mówi: „to programowy interfejs, tu będzie JSON, i to jest część wersji v1".
Po trzecie, /api pomaga omawiać kontrakt. Gdy mówisz „nasze API — to wszystko pod /api/v1", formułujesz granicę bez dodatkowych wyjaśnień.
Jeśli lubisz analogie, to /api — jak osobny terminal na lotnisku. Możesz mieć terminal dla lotów krajowych, międzynarodowych, cargo, VIP. Mieszanie wszystkiego w jednym wejściu jest możliwe, ale potem długo będziesz odpowiadał na pytania „gdzie odebrać bagaż?". API — to jak bagaż: lepiej od razu postawić drogowskazy.
3. Wersja /v1: ustalamy od razu
Wersję w ścieżce lepiej ustalić od razu, nawet w projekcie edukacyjnym. Wersjonowanie często jest postrzegane jako rozmowa „dla dużych", gdzie są miliony klientów i enterprise-polityki. Ale nie potrzebujemy enterprise, żeby zrozumieć prostą ideę: kontrakt żyje dłużej niż kod. Kod przepiszesz, a klient może żyć latami. I jeśli nie zostawiłeś sobie przestrzeni do zmiany adresów, w przyszłości będziesz je zmieniał albo boleśnie, albo bardzo brzydko.
Segment /v1 w ścieżce — to marker wersji API, który staje się częścią publicznego adresu. Nie sprawia, że API jest „lepsze" samo w sobie, ale sprawia, że zmiany są zarządzalne. W kursie utrwalamy właśnie path-based versioning, bo to najprzejrzystszy i najwygodniejszy w nauce wariant: wersję dosłownie widać w URI gołym okiem.
Po co nam to w praktyce, nawet w projekcie edukacyjnym?
Jeśli jutro postanowisz (albo kolega „pomoże" ci) zmienić kontrakt tak, że starzy klienci się posypią, masz jasny mechanizm: nowa wersja żyje obok starej. Starzy klienci chodzą do /api/v1/..., nowi — do /api/v2/.... Nie oznacza to, że musisz obsługiwać obie wersje w nieskończoność, ale przynajmniej masz wybór i możliwość migracji.
Ważne: nie mówimy teraz o złożonych strategiach obsługi wersji, o deprecation-policy, o subtelnościach kompatybilności. Tu wystarczy podstawowa dyscyplina: wersję w adresie ustalamy od razu, żeby nie rozrywać mapy API na kawałki gdy projekt zacznie „rosnąć".
Z punktu widzenia klienta wygląda to tak samo zrozumiale jak wersja biblioteki: nie stawiasz „najnowszej niewiadomo jakiej", stawiasz „oto tę". Wersja w URI — mniej więcej ta sama myśl, tylko na poziomie adresów.
4. Jednolity styl ścieżek: bez mieszania
Po wyborze bazowej ścieżki główne niebezpieczeństwo — zacząć ją „trochę" łamać. W inżynierii „trochę łamać umowy" — to jak „trochę nie gasić ogniska w lesie": najpierw wydaje się, że nic strasznego, a potem masz pożar całego lasu i README.
Mieszanie ścieżek zazwyczaj zaczyna się niewinnie. Na przykład piszesz pierwsze żądania .http albo szkicujesz kod, i gdzieś ręka sama pisze /tasks. A potem dodajesz nowy endpoint i już robisz /api/v1/tags. I oto masz dwa style żyjące obok siebie.
Dlaczego to jest złe właśnie jako kontrakt API?
Bo klient nie musi zgadywać twoich powodów. Zobaczy dwa adresy podobne do dwóch różnych „wejść" do systemu i od razu zacznie zadawać pytania.
- „/tasks — to stara ścieżka? A /api/v1/tasks — nowa?"
- „A jeśli używam /tasks, czy na pewno będzie żyła rok?"
- „Dlaczego tagi tylko pod /api/v1, a zadania można i tak, i tak?"
W efekcie zamiast developmentu zaczynasz robić support dla własnego API. I tak, brzmi śmiesznie, dopóki masz jednego klienta — siebie. Ale gdy tylko pojawi się drugi deweloper, który nie czytał twoich myśli, wszystko staje się nieoczekiwanie realne.
Żeby do tego nie doszło, wprowadzamy prostą zasadę kursu:
Każdy publiczny endpoint projektu zaczyna się od /api/v1. Bez wyjątków.
I ta zasada dotyczy nie tylko „głównych" zasobów, ale też pod-zasobów, specjalnych ścieżek akcji i endpointów lookup. Jeśli masz wyjątki, masz drugi styl, a drugi styl — to ukryta złożoność.
Poniżej — mały przykład „luźnej mapy", która wygląda podejrzanie (nawet jeśli wszystko działa):
// Przykład tego, jak jedna "wypadła" ścieżka niszczy jednolity styl mapy API
class MixedPaths {
static final String TASKS = "/api/v1/tasks";
static final String TAGS = "/api/v1/tags";
static final String TASK_BY_ID = "/tasks/{taskId}"; // wypadło z ogólnego stylu
}
Tu problem nie w kodzie Java, lecz w tym, że jedna ścieżka wypada z ogólnej formy i od razu utrudnia czytanie mapy API wzrokiem.
5. Bazowa ścieżka w kodzie: stałe
Bazową ścieżkę lepiej trzymać w kodzie jako stałą, a nie duplikować ręcznie. Brzmi kusząco: „No co tam trzymać, będę po prostu wszędzie ręcznie pisać /api/v1". Ale praktyka pokazuje, że ręczne duplikowanie stringów — to niemal gwarantowany błąd. Gdzieś zapomnisz /v1, gdzieś postawisz zbędny slash, gdzieś przypadkowo napiszesz /api/vl (litera „l" zamiast cyfry „1", a potem dwie godziny smutno patrzysz w logi).
Dlatego nawet na wczesnym etapie warto zrobić mały „dom" dla ścieżek. Jeszcze nie implementujemy tych endpointów, ale już możemy utrwalić je jako jednolity zestaw stałych. To wystarczy, żeby ścieżka nie rozłaziła się po projekcie jako przypadkowe stringi.
package com.example.tasktracker.api;
public final class ApiPaths {
private ApiPaths() {
}
public static final String BASE = "/api/v1";
public static final String TASKS = BASE + "/tasks";
public static final String TASK_BY_ID = TASKS + "/{taskId}";
public static final String TASK_COMMENTS = TASK_BY_ID + "/comments";
public static final String TASK_COMMENT_BY_ID = TASK_COMMENTS + "/{commentId}";
public static final String TASK_ATTACHMENTS = TASK_BY_ID + "/attachments";
public static final String TASK_ATTACHMENT_BY_ID = TASK_ATTACHMENTS + "/{attachmentId}";
public static final String TASK_ATTACHMENT_DOWNLOAD = TASK_ATTACHMENT_BY_ID + "/download";
public static final String TAGS = BASE + "/tags";
}
Ten zestaw wystarczy, żeby cała mapa API czytała się z jednego miejsca: wspólny prefiks, zasoby górnopoziomowe, pod-zasoby i wąski .../download dla zawartości załącznika. Ważna nie jest forma klasy, lecz to, że BASE, nazwy zasobów i szablony nie żyją rozrzucone po projekcie.
I od razu trzymamy styl bez kończącego slasha: /api/v1/tasks, a nie /api/v1/tasks/. Wtedy ścieżki składają się przewidywalnie i bez drobnych literówek.
6. Przykłady dla Task Tracker API
Bazowa ścieżka musi jednakowo pokrywać wszystkie strefy zasobowe projektu. Jeśli teraz pojawia się szczere pytanie: „OK, /api/v1 — to korzeń. Ale jak upewnić się, że naprawdę używam go wszędzie poprawnie, nie wypisując całej mapy na dziesięciu ekranach?" — spokojnie: finalną mapę będziemy składać w następnej lekturze. Tu ważne jest tylko zobaczyć, że bazowa ścieżka łatwo „nakrywa" wszystkie zasoby projektu i pozostaje jednakowa.
W ramach domeny Task Tracker API bazowa ścieżka działa jak jednolita rama dla wszystkich stref zasobowych. Na przykład ścieżka kolekcji zadań nieuchronnie staje się: /api/v1/tasks
Pod-zasób komentarzy pozostaje tak samo „wewnątrz ramy": /api/v1/tasks/{taskId}/comments
Lookup tagów, który zdecydowaliśmy trzymać górnopoziomowo (bez zagnieżdżenia pod zadanie), też naturalnie układa się pod ten sam prefiks: /api/v1/tags
Nawet jeśli na razie nie pamiętasz całej listy przyszłych endpointów, masz już ważny nawyk: gdy wymyślasz każdą nową ścieżkę, mentalnie zaczynasz od BASE, a potem dodajesz segmenty zasobu. To banalnie proste, ale właśnie ta banalność „trzyma" API w jednolitym stylu.
Poniżej — mała tabela, która zwykle bardzo szybko wykrywa niespójność (i nie jest przy tym „finalną mapą"):
| Co to za strefa | Przykład „adresu w naszym stylu" | Co będzie wyglądało podejrzanie |
|---|---|---|
| Kolekcja zadań | /api/v1/tasks | /tasks, /taskList, /api/tasks |
| Pod-zasób komentarzy | /api/v1/tasks/{taskId}/comments | /api/v1/comments?taskId=... *to już inny styl* |
| Lookup tagów | /api/v1/tags | /api/v1/tasks/{taskId}/tags *jeśli chciałeś właśnie ogólną listę* |
Zauważ: „podejrzanie" nie oznacza „nigdy nie wolno". Ale znaczy, że powinieneś umieć wytłumaczyć, dlaczego tak u ciebie jest. W projekcie edukacyjnym świadomie wybieramy jeden kanoniczny styl i trzymamy go do końca, żeby student utrwalił dyscyplinę, a nie kolekcję wyjątków.
7. Typowe błędy przy wyborze bazowej ścieżki
Błąd №1: uważać /api/v1 za dekoracyjny string i „potem wymyślimy".
Takie podejście często prowadzi do tego, że projekt zdąży obrosnąć dziesiątkami ścieżek bez jednolitej formy, a „potem" zamienia się w ciężką przeróbkę. Poprawianie bazowej ścieżki w już żyjącym API — to nie refaktoryzacja, lecz zmiana publicznego kontraktu. Dlatego utrwalamy /api/v1 zanim zaczniemy aktywnie pisać kod.
Błąd №2: mieszać ścieżki z bazowym prefiksem i bez niego.
Bardzo częsta historia: „no, ten endpoint dla wygody zrobiłem /tasks, a reszta — /api/v1/...". W efekcie mapa API staje się luźna, klient musi trzymać w głowie dwa style, a deweloperzy zaczynają pisać żądania .http i testy w różny sposób. Bazowa ścieżka działa tylko gdy jest stosowana wszędzie bez wyjątków.
Błąd №3: kopiować /api/v1 po projekcie ręcznie i dopuszczać rozbieżności.
Gdy tylko dwa razy ręcznie powtórzyłeś string, stworzyłeś możliwość desynchronizacji. Jeden deweloper napisał /api/v1, inny — /api/vl, trzeci dodał kończący slash. Rozwiązuje się to nudno i właściwie: jeden BASE, dalej wszystko budujemy od niego. Nudne rozwiązania w architekturze — to często najniezawodniejsze rozwiązania.
Błąd №4: „rozmywać" wersję po różnych formatach.
Czasem spotyka się sytuację, gdy część ścieżek jest wersjonowana przez /v1, część — wcale, część — przez nagłówki albo parametr query. W prawdziwym życiu takie schematy bywają, ale dla projektu edukacyjnego to niemal gwarantowane zamieszanie. Wybieramy jeden przejrzysty sposób: wersja w ścieżce. Jeśli podjąłeś decyzję o /api/v1, powinna być jednolita.
Błąd №5: próbować ukrywać złożoność w „sprytnych generatorach" ścieżek.
Początkujący czasem zaczynają robić ogromne klasy PathBuilder, które zamieniają proste stringi w mini-DSL. W efekcie zamiast zrozumiałego kontraktu dostają kolejną warstwę, którą trzeba poznawać. Na tym etapie wystarczą stałe i małe funkcje pomocnicze. Naszym celem jest jasność, a nie mistrzostwa w abstrakcjach.
GO TO FULL VERSION