
Jak trudne może być wersjonowanie API? Prawda jest taka, że nie jest to trudne, ale to, co jest trudne, to utrzymanie pewnej zdroworozsądkowej perspektywy, aby nie bezsensownie zbaczać w oszałamiającą liczbę wersji i podwersji stosowanych w dziesiątkach punktów końcowych API o niejasnych kompatybilnościach.
Złe zmiany! API Versioning dobre!
Każdy, kto budował lub regularnie używa API, prędzej czy później zdaje sobie sprawę, że zmiany łamiące są bardzo złe i mogą być bardzo poważnym krzyżem dla użytecznego API. Zmiana łamiąca to zmiana w funkcjonowaniu API, która może zepsuć integrację użytkownika i spowodować wiele frustracji oraz utratę zaufania między dostawcą a użytkownikiem API. Zmiany łamiące wymagają, aby użytkownicy byli powiadamiani z wyprzedzeniem (z towarzyszącymi przeprosinami), zamiast zmian, które po prostu się pojawiają, jak na przykład przyjemna nowa funkcja. Sposobem uniknięcia tej frustracji jest wprowadzenie wersji API z zapewnieniem od właściciela API, że nie będą wprowadzane żadne zaskakujące zmiany w obrębie jednej wersji.
Więc jak trudne może być wersjonowanie API? Prawda jest taka, że to nie jest trudne, ale trudne jest utrzymanie zdrowia psychicznego poprzez niepotrzebne rozwijanie się w zawrotną liczbę wersji i podwersji stosowanych na dziesiątkach punktów końcowych API z niejasnymi zgodnościami.
Wprowadziliśmy v1 API trzy lata temu i nie zdawaliśmy sobie sprawy, że będzie ono tak silne do dziś. Jak więc udało nam się dalej zapewniać najlepsze API do dostarczania e-maili od ponad dwóch lat, ale wciąż utrzymując tę samą wersję API? Ta stabilność jest kluczowa dla deweloperów budujących aplikacje z email APIs w infrastrukturze chmurowej, gdzie niezawodność i spójność są najważniejsze. Mimo że istnieje wiele różnych opinii na temat wersjonowania REST API, mam nadzieję, że historia naszego skromnego, ale potężnego v1 może Cię prowadzić na drodze do oświecenia w wersjonowaniu API.
REST Is Best
API Governance
Po wybraniu konwencji wersjonowania mieliśmy więcej pytań. Kiedy powinniśmy zmienić wersję? Co jest zmianą powodującą niekompatybilność? Czy powinniśmy zmienić wersję całego API czy tylko niektóre punkty końcowe? W SparkPost mamy wiele zespołów pracujących nad różnymi częściami naszego API. W obrębie tych zespołów, osoby pracują nad różnymi punktami końcowymi w różnych momentach. Dlatego bardzo ważne jest, aby nasze API było spójne w zakresie używania konwencji. To było coś więcej niż tylko wersjonowanie.
Utworzyliśmy grupę zarządzającą, w skład której weszli inżynierowie reprezentujący każdy zespół, członek zespołu zarządzania produktem oraz nasz CTO. Ta grupa jest odpowiedzialna za ustanowienie, dokumentację i egzekwowanie konwencji dotyczących API we wszystkich zespołach. Kanał Slack zarządzania API również przydaje się do ożywionych debat na ten temat.
Grupa zarządzająca zidentyfikowała kilka sposobów wprowadzania zmian do API, które są korzystne dla użytkownika i nie powodują niekompatybilności. Obejmują one:
Nowy zasób lub punkt końcowy API
Nowy opcjonalny parametr
Zmiana w niepublicznym punkcie końcowym API
Nowy opcjonalny klucz w treści POST JSON
Nowy klucz zwracany w treści odpowiedzi JSON
Z kolei zmiana powodująca niekompatybilność obejmowała wszystko, co mogłoby przerwać integrację użytkownika, takie jak:
Nowy wymagany parametr
Nowy wymagany klucz w treściach POST
Usunięcie istniejącego punktu końcowego
Usunięcie istniejącej metody żądania punktu końcowego
Zasadniczo różne wewnętrzne zachowanie wywołania API – na przykład zmiana domyślnego zachowania.
The Big 1.0
W trakcie dokumentowania i omawiania tych konwencji doszliśmy również do wniosku, że w naszym, a także wszystkich innych interesie leży unikanie wprowadzania zmian powodujących problemy z API, ponieważ zarządzanie wieloma wersjami wymaga dużo dodatkowej pracy. Zdecydowaliśmy, że jest kilka rzeczy, które powinniśmy naprawić w naszym API przed zatwierdzeniem do „v1”.
Wysłanie prostego e-maila wymagało zbyt wiele wysiłku. Aby „zachować prostotę”, zaktualizowaliśmy treść POST, aby zapewnić, że zarówno proste, jak i złożone przypadki użycia są uwzględnione. Nowy format był również bardziej odporny na przyszłe zmiany. Po drugie, zajęliśmy się problemem z punktem końcowym Metrics. Ten punkt końcowy używał parametru „group_by”, który zmieniał format treści odpowiedzi GET, tak że pierwszym kluczem była wartość parametru group_by. Nie wydawało się to zgodne z REST, więc podzieliliśmy każde group_by na osobny punkt końcowy. Na koniec przeprowadziliśmy audyt każdego punktu końcowego i wprowadziliśmy drobne zmiany tu i tam, aby zapewnić ich zgodność ze standardami.
Dokumentacja Accurate
Ważne jest posiadanie dokładnej i użytecznej dokumentacji API, aby unikać wprowadzania niezamierzonych lub zamierzonych zmian, które mogą ją zepsuć. Zdecydowaliśmy się na proste podejście do dokumentacji API, wykorzystując język Markdown zwany API Blueprint i manage our docs in Github. Nasza społeczność przyczynia się do ulepszania tych otwartych dokumentów. Utrzymujemy również niepubliczny zestaw dokumentów w Github dla wewnętrznych API i punktów końcowych.
Początkowo publikowaliśmy nasze dokumenty w Apiary, świetnym narzędziu do prototypowania i publikowania dokumentacji API. Jednak osadzanie Apiary na naszej stronie internetowej nie działa na urządzeniach mobilnych, dlatego teraz używamy Jekyll do generowania dokumentacji statycznej. Nasze najnowsze SparkPost API docs ładują się szybko i działają dobrze na urządzeniach mobilnych, co jest ważne dla developerów, którzy nie zawsze siedzą przy komputerze.
Separating Deployment from Release
Na początku nauczyliśmy się cennej sztuczki oddzielania wdrożenia od wydania. Dzięki temu możemy często wdrażać zmiany, gdy są gotowe, za pomocą ciągłego dostarczania i wdrażania, ale nie zawsze ogłaszamy je publicznie lub dokumentujemy w tym samym czasie. Nie jest niczym niezwykłym, że wdrażamy nowy punkt końcowy API lub ulepszenie istniejącego punktu końcowego API i używamy go w interfejsie użytkownika lub za pomocą narzędzi wewnętrznych zanim go publicznie udokumentujemy i wspieramy. W ten sposób możemy wprowadzać do niego drobne poprawki pod kątem użyteczności lub zgodności ze standardami, nie martwiąc się o wprowadzanie niechcianych zmian powodujących problemy. Gdy jesteśmy zadowoleni ze zmiany, dodajemy ją do naszej dokumentacji publicznej.
Cholera!
Jest tylko uczciwie przyznać, że były czasy, kiedy nie sprostaliśmy naszym ideałom „bez zmian łamiących” i warto się z nich uczyć. W jednym przypadku uznaliśmy, że lepiej byłoby dla użytkowników, gdyby pewna właściwość domyślnie wynosiła true zamiast false. Po wprowadzeniu zmiany otrzymaliśmy od użytkowników kilka skarg, ponieważ zachowanie zmieniło się nieoczekiwanie. Cofnęliśmy zmianę i dodaliśmy ustawienie na poziomie konta – zdecydowanie bardziej przyjazne podejście dla użytkownika.
Czasami kusi nas wprowadzanie zmian łamiących w wyniku napraw błędów. Jednak zdecydowaliśmy się pozostawić te idiosynkrazje w spokoju, zamiast ryzykować łamanie integracji klientów dla zachowania spójności.
Są rzadkie przypadki, kiedy podjęliśmy poważną decyzję o wprowadzeniu zmiany łamiącej – na przykład wycofanie zasobu lub metody API – w interesie większej społeczności użytkowników i tylko po potwierdzeniu, że ma to niewielki lub żaden wpływ na użytkowników. Na przykład celowo podjęliśmy decyzję o zmianie zachowania odpowiedzi API Suppression, ale dopiero po dokładnym rozważeniu korzyści i wpływu na społeczność oraz dokładnym komunikowaniu zmiany naszym użytkownikom. Jednak nigdy nie wprowadzilibyśmy zmiany, która ma nawet najmniejsze prawdopodobieństwo bezpośredniego wpływu na wysyłanie emaila produkcyjnego użytkownika.