2015-04-25 22 views
59

Czytałem już strategie wersjonowania dla interfejsów API ReST i coś, czego nie wydaje się adresować, to sposób zarządzania podstawową bazą kodów.Jak zarządzać podstawową bazą kodową dla wersji API?

Powiedzmy robimy kilka łamanie zmian API - na przykład, zmieniając nasz zasób klienta tak, że zwraca oddzielne forename i surname pól zamiast pojedynczego name dziedzinie. (W tym przykładzie użyję rozwiązania do porównywania adresów URL, ponieważ łatwo jest zrozumieć związane z nim pojęcia, ale pytanie dotyczy negocjacji treści lub niestandardowych nagłówków HTTP)

Mamy teraz punkt końcowy pod numerem http://api.mycompany.com/v1/customers/{id}, a inny niezgodny punkt końcowy pod numerem http://api.mycompany.com/v2/customers/{id}. Wciąż publikujemy poprawki błędów i aktualizacje zabezpieczeń do interfejsu API w wersji 1, ale rozwój nowych funkcji skupia się teraz na v2. Jak zapisujemy, testujemy i wdrażamy zmiany na naszym serwerze API? Widzę co najmniej dwa rozwiązania:

  • Użyj kontroli źródła gałąź/etykietę na kodzie v1. v1 i v2 są opracowywane i wdrażane niezależnie, a scalenia kontroli wersji są używane w razie potrzeby, aby zastosować tę samą poprawkę do obu wersji - podobnie do zarządzania bazami kodów dla natywnych aplikacji podczas opracowywania nowej dużej wersji, przy jednoczesnym wspieraniu poprzedniej wersji.

  • Uczyń samą bazę kodów o wersjach API, dzięki czemu otrzymasz jedną bazę kodów, która zawiera zarówno reprezentację klienta v1, jak i reprezentację klienta v2. Traktuj wersję jako częś ć architektury rozwiĘ ... zania zamiast problemu z instalacjĘ ... - prawdopodobnie używajĘ ... c pewnej kombinacji przestrzeni nazw i routingu, aby upewnić się, że próby sĘ ... obsługiwane przez poprawnĘ ... wersję.

Oczywistą zaletą modelu oddziału jest to, że jest to trywialne, aby usunąć stare wersje API - wystarczy zatrzymać wdrożenie odpowiedniego oddziału/tag - ale jeśli używasz kilka wersji, może skończyć się z bardzo zawiłe struktura oddziałów i potok wdrażania. Model "ujednoliconej bazy kodów" pozwala uniknąć tego problemu, ale (myślę, że?) Znacznie utrudniłoby usuwanie przestarzałych zasobów i punktów końcowych z kodu, gdy nie są już potrzebne. Wiem, że jest to prawdopodobnie subiektywne, ponieważ jest mało prawdopodobne, aby było prostą poprawną odpowiedzią, ale jestem ciekawy, w jaki sposób organizacje, które utrzymują złożone API w wielu wersjach, rozwiązują ten problem.

+18

Dzięki za pytanie! Nie mogę uwierzyć, że więcej ludzi nie odpowiada na to pytanie !! Jestem chory i zmęczony tym, że wszyscy mają opinię na temat tego, jak wersje wchodzą do systemu, ale nikt nie wydaje się stawić czoła prawdziwemu trudnemu problemowi z wysłaniem wersji do ich właściwego kodu. Do tej pozornie powszechnego problemu powinno już być co najmniej szereg akceptowanych "wzorców" lub "rozwiązań". Istnieje szalona liczba pytań na SO dotyczących "wersji API". Decydując się na akceptację wersji jest FRIKKIN SIMPLE (relatywnie)! Posługiwanie się nią w bazie kodu, gdy już się dostanie, jest trudne! – arijeet

Odpowiedz

22

Użyłem obu strategii, o których wspomniałeś. Z tych dwóch faworyzuję drugie podejście, będąc prostsze, w przypadkach użycia, które go wspierają.Oznacza to, że jeśli potrzeby wersjonowania są proste, a następnie przejść z prostszej konstrukcji oprogramowania:

  • niewielka liczba zmian, niskie zmian złożoności lub harmonogramu zmian niskiej częstotliwości
  • zmian, które w dużej mierze prostopadła do reszty kodu: publiczny interfejs API może istnieć spokojnie z resztą stosu bez konieczności "nadmiernego" (bez względu na definicję tego terminu, którą wybierzesz) rozgałęzienia w kodzie

Nie uważam tego za zbyt trudnego aby usunąć przestarzałe wersje używające tego modelu:

  • dobry zasięg testu oznacza, że ​​zgrywania się emerytowana API i powiązany kod podkładową zapewnić nie (dobrze, minimal) regresje
  • dobrą strategię nazewnictwa (nazwy pakietów API numerów wersji, czyli nieco brzydsze, wersje API w nazwach metoda) ułatwiło zlokalizowanie odpowiedniego kodu
  • Problemy przekrojowe są trudniejsze; modyfikacje podstawowych systemów zaplecza w celu obsługi wielu interfejsów API muszą być bardzo ostrożnie ważone. W pewnym momencie koszt wersji backendu (patrz komentarz na temat "nadmiernego" powyżej) przeważa nad korzyścią z pojedynczego kodu.

Pierwsze podejście jest z pewnością prostsze z punktu widzenia zmniejszenia konfliktu między istniejącymi wersjami, ale obciążenie związane z utrzymaniem oddzielnych systemów ma tendencję do przeważania nad korzyścią ograniczenia konfliktu wersji. Powiedział, że nie było łatwo postawić nowy publiczny stos API i rozpocząć iterację w oddzielnym oddziale API. Oczywiście strata pokoleniowa ustawiona jest niemal natychmiast, a gałęzie zamieniły się w chaos scaleń, łączą rozwiązania konfliktów i inną taką zabawę.

Trzecie podejście polega na warstwie architektonicznej: przyjmij wariant wzoru Fasady i zamień swoje interfejsy API na ogólnodostępne, wersjonowane warstwy, które komunikują się z odpowiednią instancją Fasady, która z kolei rozmawia z serwerem za pośrednictwem własnego zestawu interfejsów API. Twoja fasada (użyłem adaptera w moim poprzednim projekcie) staje się własnym pakietem, samodzielnym i testowalnym i pozwala na migrowanie interfejsów API frontendu niezależnie od zaplecza i od siebie nawzajem.

To zadziała, jeśli wersje interfejsu API będą wykazywać te same rodzaje zasobów, ale z różnymi reprezentacjami strukturalnymi, jak w przypadku twojego imienia i nazwiska/imienia/nazwiska. To staje się nieco trudniejsze, jeśli zaczynają polegać na różnych obliczeniach backendu, np. "Moja usługa backendu zwróciła niepoprawnie wyliczone odsetki złożone, które zostały ujawnione w publicznym interfejsie API v1. Nasi klienci już załatali to nieprawidłowe zachowanie. Dlatego nie mogę tego zaktualizować obliczenia w zapleczu i mają zastosowanie do wersji 2. Dlatego musimy teraz rozwikłać nasz kod obliczania odsetek. " Na szczęście nieczęsto się zdarza: praktycznie rzecz biorąc, użytkownicy interfejsów API RESTful faworyzują dokładne reprezentacje zasobów w porównaniu z kompatybilnością wsteczną w poszukiwaniu błędów, nawet wśród niezakłócających zmian teoretycznie idempotentnych zasobów.

Będę zainteresowany wysłuchaniem Twojej ostatecznej decyzji.

+2

Po prostu ciekawy, w kodzie źródłowym, czy duplikujesz modele między wersjami v0 i v1, które się nie zmieniły? Czy masz v1 używać niektórych modeli v0? Dla mnie byłbym zdezorientowany, gdybym zobaczył v1 używający modeli v0 dla niektórych pól. Z drugiej jednak strony zmniejszyłoby to wzrost kodu. Czy w przypadku obsługi wielu wersji wystarczy zaakceptować i zastosować kod zdublowany w przypadku modeli, które nigdy się nie zmieniły? – EdgeCaseBerg

+0

Moje wspomnienie polega na tym, że nasze wersje kodu źródłowego modelują się niezależnie od samego API, więc na przykład API v1 może używać Modelu V1, a API v2 może również używać Modelu V1. Zasadniczo wewnętrzny wykres zależności dla publicznego interfejsu API zawierał zarówno odsłonięty kod API, jak i kod "realizacji" zaplecza, taki jak kod serwera i modelu. W przypadku wielu wersji jedyną strategią, jaką kiedykolwiek stosowałem, jest dublowanie całego stosu - podejście hybrydowe (moduł A jest duplikowany, moduł B jest wersjonowany ...) wydaje się być bardzo zagmatwany. YMMV oczywiście. :) – Palpatim

2

Rozgałęzienie wydaje mi się dużo lepsze i zastosowałem to podejście w moim przypadku.

Tak, jak już wspomniano - przywracanie poprawek będzie wymagało pewnego wysiłku, ale jednocześnie obsługa wielu wersji w ramach jednej bazy źródłowej (z routingiem i wszystkimi innymi) będzie wymagać, jeśli nie mniej, ale przynajmniej takiego samego wysiłku , czyniąc system bardziej skomplikowanym i monstrualnym, z różnymi gałęziami logiki w środku (w pewnym momencie wersji na pewno dojdziesz do ogromnego case() wskazując na moduły wersji mające duplikat kodu, lub jeszcze gorzej if(version == 2) then...). Nie zapomnij również, że dla celów regresji nadal musisz rozgałęziać testy.

Jeśli chodzi o zasady dotyczące wersji, to powinienem zachować maksymalnie -2 wersje z bieżącego, wycofując wsparcie dla starych - które mogłyby skłonić użytkowników do przeniesienia.

4

Dla mnie drugie podejście jest lepsze. Używam go do usług sieciowych SOAP i planuję użyć go również do REST.

Podczas pisania kod źródłowy powinien być świadomy wersji, ale warstwa kompatybilności może być używana jako oddzielna warstwa. W twoim przykładzie baza kodów może generować reprezentację zasobów (JSON lub XML) z imionami i nazwiskami, ale warstwa kompatybilności zmieni ją tak, by miała tylko nazwę.

Baza kodowa powinna implementować tylko najnowszą wersję, powiedzmy v3. Warstwa kompatybilności powinna konwertować żądania i odpowiedzi między najnowszą wersją v3 a obsługiwanymi wersjami, np. V1 i v2. Warstwa zgodności może mieć oddzielne adaptery dla każdej obsługiwanej wersji, która może być podłączona jako łańcuch.

Na przykład: wniosek v1

Client: V1 dostosować do v2 ---> v2 dostosować V3 ----> CODEBASE wniosek v2

klient: V1 dostosować do v2 (pomiń) - -> v2 dostosuj do v3 ----> baza kodów

W celu uzyskania odpowiedzi adaptery działają po prostu w przeciwnym kierunku. Jeśli używasz środowiska Java EE, możesz na przykład użyć łańcucha filtrów serwletów jako łańcucha adapterów.

Usunięcie jednej wersji jest łatwe, usuń odpowiedni adapter i kod testowy.