Zespół publikuje pierwszą wersję interfejsu w marcu. W czerwcu okazuje się, że pole status potrzebuje nowej wartości. Dodają ją — to przecież rozszerzenie, nic nie usuwają. W lipcu dzwoni największy klient integracyjny: ich system przestał przyjmować zamówienia, bo waliduje odpowiedzi ścisłym schematem, a nieznana wartość jest dla niego błędem.
Nikt niczego nie zepsuł. Ktoś tylko odkrył, że w marcu — nie wiedząc o tym — obiecał, że zbiór wartości tego pola jest zamknięty.
Nazywam się Bartosz Ciepierski, kieruję ARDURA Consulting i przez lata pracy przy programach integracyjnych doszedłem do wniosku, że większość poradników o projektowaniu API odpowiada na złe pytanie. Pytają, jak zaprojektować dobry interfejs. Właściwe pytanie brzmi: czego nie obiecywać, żeby dało się go później zmienić.
Stąd teza tego tekstu, z którą kompetentny czytelnik ma pełne prawo się nie zgodzić: wersjonowanie API nie jest dobrą praktyką — jest rachunkiem za obietnice złożone szerzej, niż było trzeba. Każda decyzja projektowa jest obietnicą, a obietnicy złożonej publicznie nie da się wycofać bez czyjejś awarii. Praktyki warte stosowania to dokładnie te, które utrzymują powierzchnię obietnic małą.
Ta checklista jest więc uporządkowana inaczej niż zwykle — nie tematycznie, tylko według jednego kryterium: co ta decyzja obiecuje na zawsze i ile kosztuje jej cofnięcie. Rzeczy, które mamy opisane osobno, celowo tu nie powtarzam: czym jest REST — przewodnik architektoniczny po REST API; jak zapisać kontrakt — OpenAPI Specification; jak chronić interfejs — API security; jak weryfikować zgodność testem — contract testing w mikrousługach. Tutaj jest o momencie wcześniejszym niż każdy z nich: o decyzji, której żaden test ani żadna bramka już nie odwróci.
Dlaczego wersjonowanie nie jest dobrą praktyką, tylko rachunkiem?
Zacznijmy od tego, co wersja naprawdę kosztuje, bo w rozmowie projektowej wygląda na zabezpieczenie, a jest zobowiązaniem.
Wypuszczenie drugiej wersji oznacza utrzymywanie dwóch implementacji, dwóch zestawów testów, dwóch dokumentacji i dwóch ścieżek diagnostycznych przez cały okres wsparcia pierwszej. Oznacza też, że każda poprawka błędu wymaga decyzji, czy dotyczy obu wersji, a każdy incydent zaczyna się od pytania, której wersji dotyczy. W praktyce koszt utrzymania dwóch wersji jest wyraźnie wyższy niż dwukrotność kosztu jednej, bo dochodzi koszt utrzymywania spójności między nimi.
Do tego dochodzi rzecz, o której mówi się rzadko: wersja nie jest odwracalna. Publikując drugą wersję, obiecujecie utrzymanie pierwszej, a moment jej wyłączenia stanie się negocjacją z każdym konsumentem osobno. Interfejsy, które miały dwie wersje, zwykle mają potem cztery.
Nie znaczy to, że wersjonowania należy unikać za wszelką cenę — czasem jest jedynym wyjściem. Znaczy natomiast, że jego pojawienie się w planie jest sygnałem diagnostycznym, a nie osiągnięciem. Zespół, który po pół roku wypuszcza drugą wersję, powinien zadać sobie pytanie, która konkretnie obietnica z pierwszej okazała się za szeroka — bo prawie zawsze da się ją wskazać i prawie zawsze była jedna.
Praktyczna konsekwencja dla planowania: zanim wybierzecie strategię wersjonowania, wykonajcie przegląd obietnic. To jest tańsze i skuteczniejsze niż jakikolwiek wybór między wersją w ścieżce a wersją w nagłówku.
Warto też zauważyć, komu ten rachunek się wystawia. Koszt wersjonowania ponosicie wy, ale koszt jego braku ponoszą konsumenci — i to jest asymetria, która sprawia, że decyzje o zmianie kontraktu bywają podejmowane zbyt lekko. Zespół widzi w swoim planie jedną pozycję z jednym dniem pracy; nie widzi kilkudziesięciu integratorów, u których ta sama zmiana uruchamia analizę, poprawkę, testy i wydanie. Jeżeli waszym interfejsem zarządza ktoś po stronie biznesu, warto tę arytmetykę pokazać wprost — wprowadzenie do samego pojęcia interfejsu w języku nietechnicznym mamy w tekście co to jest API.
Co właściwie obiecujecie, publikując pierwszy punkt końcowy?
Znacznie więcej, niż jest zapisane w dokumentacji — i to jest sedno problemu.
Obietnice dzielą się na trzy warstwy. Warstwa zadeklarowana to wszystko, co napisaliście: ścieżki, pola, typy, kody odpowiedzi. Tę warstwę kontrolujecie. Warstwa wynikająca to własności, które nie zostały napisane, ale wynikają z zachowania: kolejność elementów w kolekcji, format identyfikatora, zakres wartości pola liczbowego, czas odpowiedzi. Warstwa zaobserwowana to wszystko, co konsument zmierzył i na czym oparł swój kod, łącznie z rzeczami, o których nie wiecie — na przykład tym, że pole opisowe nigdy nie było dłuższe niż sto znaków, więc zapisał je w kolumnie o takiej szerokości.
Prawo Hyruma opisuje to jednym zdaniem: przy dostatecznej liczbie użytkowników każde obserwowalne zachowanie systemu staje się dla kogoś zobowiązaniem, niezależnie od tego, co obiecuje kontrakt. W praktyce oznacza to, że powierzchnia waszych obietnic jest równa powierzchni tego, co widać, a nie temu, co udokumentowaliście.
Stąd cztery reguły projektowe wynikające wprost z tezy, które warto przyjąć zanim opublikujecie cokolwiek:
- Nie ujawniajcie tego, czego nie musicie. Każde dodatkowe pole w odpowiedzi to obietnica na zawsze. Pole dodane „bo się przyda” jest polem, którego nie usuniecie.
- Deklarujcie zamiar tam, gdzie milczenie zostanie odczytane jako obietnica. Jeżeli kolejność jest nieokreślona, napiszcie to wprost. Jeżeli zbiór wartości będzie się rozszerzał, napiszcie to i opiszcie, jak konsument ma traktować wartość nieznaną.
- Publikujcie mniej, wcześniej. Wąski interfejs, który da się rozszerzyć, jest lepszy od szerokiego, którego nie da się zawęzić — rozszerzenie jest zwykle zgodne wstecz, zawężenie nigdy.
- Traktujcie dokumentację jako granicę, nie jako opis. Kontrakt zapisany w specyfikacji maszynowej, generowanej z kodu albo weryfikowanej wobec kodu, jest granicą sprawdzalną; kontrakt opisany prozą jest życzeniem.
Jak odróżnić zmianę zgodną wstecz od niezgodnej, skoro intuicja tu myli?
Intuicja podpowiada, że dodawanie jest bezpieczne, a usuwanie nie. To jest za grube i odpowiada za większość awarii integracyjnych.
Zmiany bezpieczne prawie zawsze: dodanie nowego punktu końcowego, dodanie pola opcjonalnego w żądaniu, dodanie pola w odpowiedzi jeśli konsumenci nie walidują ściśle, rozluźnienie walidacji, dodanie nowego kodu błędu w obrębie tej samej klasy odpowiedzi.
Zmiany niezgodne wstecz, choć wyglądają niewinnie:
- Nowa wartość w zbiorze dopuszczalnych wartości. Dla was rozszerzenie; dla konsumenta z pełnym rozgałęzieniem po znanych wartościach albo ze ścisłą walidacją schematu — błąd wykonania. Jedyne zabezpieczenie to zadeklarowanie z góry, że zbiór jest otwarty, i opisanie zachowania dla wartości nieznanej.
- Zaostrzenie walidacji. Pole przyjmujące dowolny tekst, które zaczyna wymagać formatu, odrzuci żądania dotąd przechodzące. Nic nie zmieniło się w dokumentacji, a integracja padła.
- Zmiana pola opcjonalnego na wymagane w żądaniu — oczywista, ale wersja odwrotna też: zmiana pola, które zawsze było obecne w odpowiedzi, na pojawiające się warunkowo.
- Zmiana kolejności elementów w kolekcji, gdy kolejność nie była udokumentowana. Konsumenci polegają na niej, bo działała.
- Zmiana formatu lub długości identyfikatora. Ktoś zapisał go w kolumnie o stałej szerokości.
- Zmiana kodu odpowiedzi dla istniejącego przypadku, na przykład z jednego kodu klasy czterysta na inny. Obsługa błędów po stronie konsumenta jest zwykle napisana na konkretnych kodach.
Reguła praktyczna, którą warto przyjąć jako definicję roboczą: niezgodne wstecz jest wszystko, co odbiera konsumentowi możliwość, na której mógł polegać — nie tylko to, co usuwacie z dokumentacji. A ponieważ „mógł polegać” jest szersze niż „obiecaliście”, jedynym wiarygodnym sprawdzeniem jest test wykonany po stronie konsumenta, o czym piszemy w tekście o contract testingu.
Czy wybór między REST a GraphQL to decyzja techniczna, czy obietnica?
To pytanie pada w każdym projekcie i prawie zawsze jest prowadzone jako spór o wygodę: nadmiarowe pobieranie danych, liczba żądań na ekranie mobilny, elastyczność klienta. Warto poprowadzić je inaczej — przez powierzchnię obietnic, bo z tej perspektywy różnica jest znacznie większa, niż wynika ze zwykłego porównania.
Interfejs w stylu REST obiecuje skończony zbiór operacji. Wystawiacie punkty końcowe, które sami zaprojektowaliście, i wiecie, jakie zapytania trafią do bazy, bo ich kształt jest ustalony. Powierzchnia obietnic jest duża, ale policzalna: da się ją wypisać, przejrzeć i objąć testami.
Interfejs w stylu GraphQL obiecuje coś jakościowo innego: że konsument sam ułoży zapytanie. To jest ogromna wartość dla zespołu klienckiego i jednocześnie obietnica o powierzchni, której nie da się wypisać, bo liczba możliwych kształtów zapytania rośnie wykładniczo wraz z liczbą powiązań w schemacie. Konsekwencje ujawniają się nie przy projektowaniu, tylko przy skali: zapytanie o głębokiej zagnieżdżonej strukturze potrafi wygenerować kaskadę odpytań do bazy, której nikt nie przewidział, a ograniczenie tego po fakcie — limitem głębokości albo kosztem zapytania — jest zawężeniem tego, co wcześniej działało, czyli zmianą łamiącą.
Praktyczna konsekwencja dla naszej tezy jest taka, że GraphQL jest tańszy tam, gdzie konsument i dostawca zmieniają się razem, czyli w obrębie jednej organizacji i jednego cyklu wydawniczego. Tam obietnica jest odwoływalna, bo obie strony siedzą przy jednym stole. W interfejsie publicznym, z konsumentami, których nie znacie i których nie da się poprosić o zmianę, ta sama elastyczność staje się zobowiązaniem bez daty końcowej.
Trzeci wariant, o którym warto pamiętać, to interfejs zdarzeniowy — publikacja zdarzeń zamiast odpowiadania na pytania. Ma inny profil obietnic: obiecujecie format zdarzenia i porządek dostarczania, ale nie obiecujecie czasu odpowiedzi ani kształtu zapytania. Dla integracji między systemami wewnętrznymi bywa to najtańsza forma zobowiązania, o czym wspominamy w tekście o architekturze mikrousług, a warstwę wejściową dla wszystkich trzech wariantów opisuje osobno tekst o API Gateway.
Dlaczego identyfikator jest najdroższą decyzją w całym projekcie?
Bo jest jedyną wartością, którą konsumenci zapisują u siebie na stałe. Pola odpowiedzi konsument odczyta ponownie; identyfikator przeniesie do własnej bazy danych, umieści w swoich raportach, wydrukuje na dokumencie i poda w zgłoszeniu serwisowym za trzy lata.
Trzy typowe wybory i ich pełny koszt.
Kolejny numer całkowity. Najprostszy i najgorszy dla interfejsu publicznego. Pozwala odgadywać cudze zasoby przez samo zwiększanie wartości, co jest podatnością, a nie niedogodnością. I ujawnia wolumen waszego biznesu każdemu, kto założy konto w styczniu i drugie w lutym, a potem odejmie numery — to jest wyciek informacji handlowej, którego nie widać w żadnym audycie bezpieczeństwa, bo formalnie nic nie zostało ujawnione.
Identyfikator losowy. Usuwa oba problemy. Kosztuje utratę naturalnej sortowalności, co w bazie danych o dużym wolumenie potrafi być odczuwalne przy wstawianiu, bo losowe wartości rozpraszają zapisy po indeksie.
Wariant uporządkowany czasowo. Nowsze warianty identyfikatorów uniwersalnych, ustandaryzowane przez IETF w dokumencie RFC 9562, łączą losowość z uporządkowaniem według czasu utworzenia. Dla większości nowych projektów jest to dziś rozsądny domyślny wybór.
Niezależnie od wyboru obowiązuje jedna zasada wynikająca z tezy: identyfikatora nie da się zmienić po publikacji. Jeśli musicie, jedyną drogą jest utrzymywanie mapowania starego na nowy — w nieskończoność, bo nigdy nie dowiecie się, kiedy ostatni konsument przestał używać starej wartości. To nie jest migracja z datą zakończenia; to jest nowe, trwałe zobowiązanie dołożone do poprzedniego.
Jak zaprojektować błędy, żeby nie trzeba było ich zmieniać?
Format błędu jest obietnicą równie trwałą jak format zasobu, a projektuje się go zwykle w pośpiechu, na końcu.
Najtaniej jest nie projektować go samodzielnie. Dokument RFC 9457 opisuje szczegóły problemu dla interfejsów HTTP jako gotowy format z typem nośnika application/problem+json, dający pola na identyfikator typu problemu, tytuł, kod stanu, opis konkretnego wystąpienia i wskazanie zasobu — z możliwością dołożenia własnych pól dla błędów walidacji poszczególnych atrybutów. Użycie standardu ma tę zaletę, że konsument, który już go zna, nie musi uczyć się waszego wynalazku.
Trzy decyzje są tu jednak ważniejsze od samego formatu.
Kod maszynowy musi być stabilny i niezależny od komunikatu. Konsumenci będą się na nim opierać niezależnie od tego, czy im na to pozwolicie — jeśli nie dacie im stabilnego kodu, oprą się na treści komunikatu, a wtedy poprawa literówki w tekście stanie się zmianą łamiącą integrację. To jest podręcznikowy przykład obietnicy złożonej przez przypadek.
Komunikat dla człowieka nie może zawierać śladu stosu, ścieżki wewnętrznej ani treści błędu bazy danych. To jest wyciek informacji o architekturze, ułatwiający atak, a nie pomocna diagnostyka. Pełny kontekst zostaje w waszych logach.
Każda odpowiedź błędna niesie identyfikator korelacyjny. Konsument poda go w zgłoszeniu, wy odnajdziecie po nim pełny kontekst. To jedna z niewielu rzeczy w tym tekście, która nie jest obietnicą, tylko czystym zyskiem operacyjnym — i którą dodaje się zwykle po pierwszym trudnym incydencie zamiast przed.
Jak stronicować i idempotentnie przyjmować żądania, żeby nie zamknąć sobie drogi?
Dwie decyzje, które wyglądają na implementacyjne, a są obietnicami na całe życie interfejsu.
Stronicowanie. Przesunięcie jest wygodne i pozwala skoczyć na dowolną stronę, ale ma dwie wady ujawniające się przy skali. Wydajnościową: baza musi przejść i odrzucić wszystkie pominięte wiersze, więc koszt strony rośnie z jej numerem. I poprawnościową, groźniejszą: jeśli między pobraniem strony trzeciej a czwartej ktoś doda rekord na początku zbioru, konsument zobaczy element dwa razy albo nie zobaczy go wcale — bez żadnej możliwości wykrycia tego po swojej stronie. Kursor zakotwicza pozycję w danych, a nie w liczbie pominiętych wierszy, i usuwa oba problemy kosztem skoku na dowolną stronę, którego interfejsy programistyczne rzadko potrzebują. Kluczowe dla naszej tezy: przejście z przesunięcia na kursor jest zmianą niezgodną wstecz, więc jest to decyzja podejmowana raz.
Idempotentność. Sieć zawiedzie, oczekiwanie klienta wygaśnie mimo poprawnego przetworzenia u was, a każda dobra biblioteka klienta ponawia żądania automatycznie. Nie da się tego zabronić — da się tylko uczynić bezpiecznym. Standardowe rozwiązanie polega na przyjęciu klucza idempotentności w nagłówku: klient generuje unikalną wartość dla operacji, wy zapamiętujecie powiązanie klucza z wynikiem pierwszego przetworzenia i przy powtórzeniu zwracacie ten sam wynik zamiast tworzyć drugi zasób. Podejście jest przedmiotem prac standaryzacyjnych IETF i upowszechniło się u dostawców płatności, gdzie cena pomyłki jest natychmiast widoczna.
Trzy szczegóły przesądzają o skuteczności: okres przechowywania kluczy musi przekraczać najdłuższe realne ponowienie; powtórzenie z tym samym kluczem, ale inną treścią, musi zostać odrzucone jako konflikt; a semantyka metod z RFC 9110 musi być respektowana, bo operacje uznane tam za idempotentne konsument będzie ponawiał bez pytania was o zdanie.
Gdzie kończy się wasza swoboda, gdy API dotyka danych osobowych i pieniędzy?
To jest warstwa, której w anglojęzycznych checklistach projektowych nie ma, bo dotyczy porządku prawnego, w którym one nie powstawały — a w polskiej organizacji potrafi unieważnić decyzję projektową po fakcie.
Identyfikatory w adresie to dane w logach. Ścieżka żądania trafia do dziennika serwera, do dziennika bramki, do dziennika sieci dostarczania treści i do systemu monitorowania — czyli do co najmniej czterech miejsc o różnych okresach przechowywania i różnych uprawnieniach dostępu. Umieszczenie w adresie numeru PESEL, adresu poczty elektronicznej albo numeru dokumentu oznacza rozsianie danych osobowych po całej infrastrukturze obserwacyjnej. Zasada minimalizacji z RODO każe użyć identyfikatora nieznaczącego, a dane identyfikujące przesyłać w treści żądania. Odwrócenie tej decyzji po roku wymaga zmiany adresów zasobów, czyli zmiany niezgodnej wstecz — i to jest właśnie przypadek, w którym warstwa prawna wymusza wersjonowanie.
Zakres pól to decyzja o podstawie przetwarzania, nie o wygodzie. Interfejs zwracający komplet danych „na wszelki wypadek”, bo tak łatwiej, przekazuje odbiorcy dane, których ten nie potrzebuje — a zawężenie zakresu później jest zmianą łamiącą. Tu ostrożność projektowa i zgodność wskazują dokładnie w tę samą stronę.
Sektor płatniczy ma kontrakt narzucony z zewnątrz. Otwarta bankowość wynikająca z dyrektywy o usługach płatniczych oraz standard interfejsu przyjęty na polskim rynku pokazują graniczny przypadek naszej tezy: gdy kontrakt jest wspólny dla całego sektora, żaden pojedynczy dostawca nie może go zmienić, choćby chciał. Swoboda projektowa spada tam praktycznie do zera — a to jest dobra ilustracja tego, w którą stronę zmierza każdy interfejs, który zdobył wielu konsumentów.
Praktyczny wniosek: decyzje projektowe dotykające danych osobowych i płatności przejrzyjcie z inspektorem ochrony danych przed publikacją, a nie po pierwszym audycie. To jedyna warstwa tego tekstu, w której cofnięcie decyzji bywa nie tylko kosztowne, ale i obowiązkowe.
Czego uczy KSeF o wersjonowaniu kontraktu narzuconego całemu rynkowi?
Krajowy System e-Faktur jest najlepszym polskim przykładem tego, o czym mówi ten tekst, bo pokazuje projektowanie API w warunkach, w których konsumentów nie da się poprosić o cierpliwość. Zgodnie z harmonogramem ustawowym obowiązek wystawiania faktur ustrukturyzowanych objął podatników o największej sprzedaży od 1 lutego 2026 roku, a pozostałych od 1 kwietnia 2026 roku — czyli w praktyce cały rynek w ciągu dwóch miesięcy.
Cztery obserwacje przenoszalne na dowolny projekt interfejsu.
Kontrakt jest opublikowanym schematem, nie prozą. Struktura logiczna faktury jest udostępniana jako schemat maszynowy, wobec którego można walidować dokument przed wysłaniem. To jest granica sprawdzalna — i różnica między nią a opisem tekstowym jest różnicą między integracją, która działa, a integracją, o której sądzimy, że działa. Ta sama zasada obowiązuje w waszym interfejsie, o czym piszemy w tekście o OpenAPI.
Wersja schematu jest wersją kontraktu. Kolejne wersje struktury logicznej wymuszają zmianę po stronie każdego integratora. Nie ma tu miejsca na „zmiana jest drobna, przecież to tylko dodatkowe pole” — przy kilkuset tysiącach podmiotów każda zmiana ma koszt zagregowany, którego pojedynczy projektant nie widzi.
Środowisko testowe jest częścią kontraktu, nie udogodnieniem. Rozdzielenie środowisk pozwala integratorowi sprawdzić zgodność przed dniem, w którym błąd oznacza brak możliwości wystawienia faktury. Interfejs bez wiarygodnego środowiska testowego przenosi koszt weryfikacji na produkcję konsumenta.
Okno przejściowe jest projektowane, a nie ogłaszane. Rynek, który ma dwa miesiące na przejście, zachowuje się inaczej niż rynek, który ma rok. To jest dokładnie ta sama decyzja, którą podejmujecie, wyznaczając okres wsparcia poprzedniej wersji — tylko w skali, w której konsekwencje widać gołym okiem.
Warto dodać piątą obserwację, mniej oczywistą. Interfejsy publikowane przez administrację — nie tylko system e-faktur, ale też wykaz podatników czy rejestry gospodarcze, z których integruje się dziś większość systemów finansowo-księgowych w Polsce — pokazują, jak wygląda limit żądań jako obietnica. Konsumenci projektują swoje procesy wsadowe pod ogłoszony limit, więc jego obniżenie łamie integracje równie skutecznie jak usunięcie pola, mimo że w żadnym schemacie nic się nie zmieniło. To jest ta sama klasa obietnicy milczącej co kolejność w kolekcji, tylko dotkliwsza, bo ujawnia się dopiero przy szczycie obciążenia. Reguła praktyczna: ogłaszajcie limit niższy, niż wytrzymacie, i podnoście go później — podniesienie jest zgodne wstecz, obniżenie nigdy.
Wniosek dla waszego projektu: jeżeli chcecie zobaczyć, ile naprawdę kosztuje zmiana kontraktu, popatrzcie na koszt, jaki poniósł rynek przy zmianie kontraktu narzuconego. Wasz interfejs generuje ten sam rodzaj kosztu, tylko rozłożony na mniejszą liczbę podmiotów — i dlatego niewidoczny w waszym rachunku.
Jak wycofać coś, co już obiecaliście?
Skoro obietnic nie da się wycofać bez czyjejś awarii, pozostaje pytanie, jak zrobić to najmniejszym kosztem. Odpowiedź składa się z czterech elementów i żaden nie jest opcjonalny.
Zmierzcie, kto tego używa. Bez telemetrii na poziomie punktu końcowego i konsumenta wycofanie jest zgadywaniem. Potrzebujecie odpowiedzi na pytanie, którzy konsumenci wywołali daną ścieżkę w ostatnich dziewięćdziesięciu dniach i z jaką częstotliwością — i to jest jedyna rzecz z tej listy, którą trzeba przygotować przed publikacją, a nie przed wycofaniem.
Zasygnalizujcie maszynowo, nie tylko w newsletterze. Nagłówek Sunset, opisany w RFC 8594, przekazuje datę, po której zasób przestanie być dostępny; towarzyszący mu nagłówek Deprecation sygnalizuje sam fakt wycofywania. Sygnał w odpowiedzi trafia do systemów, a wpis na blogu trafia do ludzi, którzy go nie czytają.
Dajcie okno proporcjonalne do kosztu zmiany po stronie konsumenta, a nie do waszej wygody. Zmiana wymagająca przepisania integracji potrzebuje kwartałów, nie tygodni. Okno krótsze od cyklu wydawniczego waszego największego konsumenta jest oknem pozornym.
Wygaszajcie stopniowo, zanim wyłączycie. Celowe spowolnienie odpowiedzi, ograniczenie limitu żądań albo krótkie okna niedostępności zapowiedziane z wyprzedzeniem — wszystko to sprawia, że konsument dowiaduje się o problemie w kontrolowanych warunkach, a nie w dniu wyłączenia. Ta technika bywa uznawana za nieelegancką; jest znacznie bardziej elegancka niż awaria u klienta.
Jest jeszcze jeden element, który bywa pomijany, a decyduje o tym, czy okno wycofania w ogóle zadziała: kontakt do konsumenta. Adres poczty podany przy rejestracji klucza dostępowego trzy lata temu zwykle należy do osoby, która już tam nie pracuje. Interfejs, który nie ma sposobu skontaktowania się z aktywnymi konsumentami, nie ma też realnej możliwości ich uprzedzenia — zostaje mu wyłącznie sygnał w odpowiedzi, czyli nadzieja, że ktoś czyta nagłówki. Utrzymywanie aktualnej listy kontaktów jest pracą nudną i wykonywaną tylko wtedy, gdy ktoś nadał jej właściciela.
Warto zauważyć, że cały ten proces jest kosztowny nawet wykonany wzorowo — i to jest ostatni argument za tezą. Skoro wycofanie obietnicy kosztuje tyle, jedynym tanim momentem, w którym można nią zarządzać, jest moment, w którym jeszcze jej nie złożyliście.
Kto pilnuje obietnic, gdy zespół się zmienia?
Wszystko powyżej zakłada, że ktoś w organizacji wie, co zostało obiecane. Po dwóch latach i dwóch rotacjach zespołu to założenie zwykle przestaje być prawdziwe — a wtedy obietnice łamie się nie przez lekkomyślność, tylko przez niewiedzę.
Cztery mechanizmy, które utrzymują tę wiedzę poza ludzką pamięcią.
Kontrakt w repozytorium, weryfikowany w potoku. Specyfikacja maszynowa przechowywana razem z kodem i porównywana przy każdej zmianie z wersją poprzednią. Narzędzia wykrywające zmiany łamiące działają dziś dobrze i wychwytują większość przypadków z sekcji o zgodności wstecz — łącznie z tymi, które człowiek przeoczy, jak zmiana pola z zawsze obecnego na warunkowe. Bramka, która przy takiej zmianie wymaga świadomej zgody zamiast po cichu przepuścić, jest najtańszym zabezpieczeniem w całym tym tekście.
Testy kontraktowe po stronie konsumenta. Weryfikacja wobec waszej specyfikacji sprawdza zgodność z tym, co zadeklarowaliście; weryfikacja wobec oczekiwań konsumenta sprawdza zgodność z tym, na czym on faktycznie polega — a to jest, jak pokazaliśmy, zbiór szerszy. Szczegóły w tekście o contract testingu, a szersze ujęcie weryfikacji interfejsu w przewodniku po testowaniu API.
Rejestr decyzji, nie tylko dokumentacja. Dokumentacja mówi, jak jest. Rejestr decyzji mówi, dlaczego tak i czego świadomie nie obiecano — a to jest jedyna informacja, która chroni przed cofnięciem decyzji przez kogoś, kto nie zna jej powodów. Trzy zdania na decyzję wystarczą; wartość bierze się z tego, że w ogóle istnieją.
Jeden właściciel kontraktu. Nie zespół, nie komitet — osoba, która akceptuje zmiany w specyfikacji. Kontrakt bez imiennego właściciela rozjeżdża się dokładnie tak samo jak kryterium wyjścia bez właściciela decyzji: ocena zbiorowa zawsze wypada „chyba można”.
Warto dodać, że wszystkie cztery mechanizmy kosztują najwięcej wtedy, gdy wdraża się je do interfejsu, który już ma konsumentów — i prawie nic, gdy powstają razem z pierwszą wersją. To ta sama asymetria, która przewija się przez cały ten tekst. Jeżeli potrzebujecie kogoś, kto przeprowadzi taki przegląd z zewnątrz, zanim kontrakt zdąży się rozjechać, mamy odpowiednie profile wśród dostępnych ekspertów.
Jak wygląda checklista obietnic i ile kosztuje cofnięcie każdej z nich?
Poniższa tabela jest streszczeniem tekstu w formie narzędzia. Kolumna z ceną cofnięcia jest w niej ważniejsza od kolumny z zaleceniem — to ona odróżnia rejestr decyzji od listy dobrych rad.
| Decyzja projektowa | Co obiecuje, także milcząco | Cena cofnięcia | Jak ograniczyć obietnicę już teraz |
|---|---|---|---|
| Format identyfikatora zasobu | Trwałość, długość, sortowalność, brak możliwości zgadywania | Najwyższa — mapowanie stary na nowy utrzymywane bezterminowo | Wybrać wariant nieznaczący i uporządkowany czasowo; nie kodować w nim znaczenia |
| Nazwy i typy pól odpowiedzi | Że pole istnieje, ma ten typ i tę semantykę | Wysoka — usunięcie pola jest zmianą łamiącą | Publikować minimum; pole dodane „bo się przyda” zostaje na zawsze |
| Zbiór dopuszczalnych wartości | Że zbiór jest zamknięty, o ile nie napisano inaczej | Wysoka, jeśli konsumenci walidują ściśle | Zadeklarować otwartość zbioru i zachowanie dla wartości nieznanej |
| Format daty i strefa czasowa | Dokładny zapis i sposób interpretacji | Wysoka — parsowanie po stronie konsumenta | Użyć zapisu zgodnego z RFC 3339 w czasie uniwersalnym, od pierwszego dnia |
| Reprezentacja kwot | Typ liczbowy, precyzja, sposób zaokrąglania, waluta | Bardzo wysoka — błędy finansowe są nieodwracalne | Kwota w najmniejszej jednostce jako liczba całkowita plus jawny kod waluty |
| Model stronicowania | Stabilność wyników i możliwość skoku na stronę | Wysoka — przejście na kursor jest zmianą łamiącą | Wybrać kursor przy pierwszej wersji |
| Format błędu i kod maszynowy | Że kod jest stabilny, a komunikat czytelny dla maszyny | Średnia, ale dotyka każdej integracji naraz | Użyć formatu z RFC 9457; kod niezależny od treści komunikatu |
| Semantyka metod i idempotentność | Że ponowienie jest bezpieczne | Średnia; brak tej obietnicy kosztuje podwójne zamówienia | Przyjmować klucz idempotentności od pierwszego dnia |
| Limity żądań | Że dopuszczalne tempo nie zmaleje | Średnia — zaostrzenie limitu łamie działających konsumentów | Ogłosić limit niższy, niż wytrzymacie; podnieść później |
| Kolejność elementów w kolekcji | Że jest stabilna, o ile nie napisano inaczej | Niska w dokumentacji, wysoka w praktyce | Napisać wprost, że jest nieokreślona — albo ją zagwarantować |
| Wersjonowanie | Że stara wersja będzie utrzymywana | Rosnąca w czasie; wersji zwykle przybywa | Nie wprowadzać, dopóki nie wskażecie obietnicy, która wymusiła zmianę |
Dwie uwagi do tej tabeli.
Po pierwsze, wiersze o najwyższej cenie cofnięcia to te, które zwykle rozstrzyga się najszybciej — identyfikator, reprezentacja kwot i format daty bywają wybierane w pierwszej godzinie prac, przez jedną osobę, bez przeglądu. To jest odwrotna kolejność niż podpowiada ryzyko.
Po drugie, ostatnia kolumna jest tym, co odróżnia tę checklistę od listy dobrych praktyk: w każdym wierszu istnieje działanie, które zawęża obietnicę i nic nie kosztuje przed publikacją. Po publikacji nie kosztuje już nic tylko dlatego, że jest niewykonalne.
Jakie są kluczowe wnioski?
Teza brzmiała: wersjonowanie nie jest dobrą praktyką, tylko rachunkiem za obietnice złożone za szeroko. Co z niej wynika praktycznie?
Projektujcie powierzchnię obietnic, nie kształt zasobów. Pytanie „jak to nazwać” jest mniej ważne od pytania „co przez to obiecuję i czy da się to cofnąć”.
Publikujcie mniej, wcześniej. Rozszerzenie jest zwykle zgodne wstecz, zawężenie nigdy. Wąski interfejs jest opcją, szeroki jest zobowiązaniem.
Deklarujcie zamiar tam, gdzie milczenie zostanie odczytane jako obietnica. Otwartość zbioru wartości, nieokreśloność kolejności, tymczasowość limitu — jedno zdanie w dokumentacji zamienia przyszłą awarię w zaplanowaną zmianę.
Rozstrzygajcie decyzje nieodwracalne najwolniej. Identyfikator, reprezentacja kwot i format daty zasługują na przegląd, a nie na pierwszą godzinę pracy jednej osoby.
Przygotujcie wycofywanie, zanim będzie potrzebne. Telemetria konsumentów na poziomie punktu końcowego jest jedyną rzeczą z tej listy, którą trzeba mieć przed publikacją, żeby móc jej użyć za trzy lata.
Jeżeli chcecie zacząć od jednego ćwiczenia: weźcie swoją obecną specyfikację i przy każdym polu odpowiedzi zapiszcie, co się stanie, jeśli za rok trzeba je będzie usunąć. Pola, przy których nie potraficie odpowiedzieć, są waszym przyszłym wersjonowaniem — i dziś jeszcze da się je usunąć za darmo.
W ARDURA Consulting prowadzimy przeglądy projektów interfejsów przed publikacją i wspieramy zespoły integracyjne w fazie wykonawczej. Jeżeli chcecie skonfrontować swoją specyfikację z tą checklistą, zanim pierwszy konsument zapisze u siebie wasze identyfikatory — napiszcie do nas przez formularz kontaktowy. Zaczniemy od kolumny z ceną cofnięcia, bo to ona pokazuje, które decyzje są jeszcze wasze.