Załóżmy, że masz duży projekt obsługiwany przez bazę API. Projekt udostępnia także publiczny interfejs API, z którego mogą korzystać użytkownicy końcowi (ish).
Czasami musisz wprowadzić zmiany w bazie API obsługującej Twój projekt. Na przykład musisz dodać funkcję, która wymaga zmiany interfejsu API, nowej metody lub wymaga zmiany jednego z obiektów lub formatu jednego z tych obiektów, przekazanych do lub z interfejsu API.
Zakładając, że używasz również tych obiektów w publicznym interfejsie API, obiekty publiczne zmienią się również za każdym razem, gdy to zrobisz, co jest niepożądane, ponieważ Twoi klienci mogą polegać na obiektach API, które pozostają identyczne, aby działał ich kod analizujący. (kaszel klientów WSDL C ++ ...)
Tak więc jednym potencjalnym rozwiązaniem jest wersja API. Ale kiedy mówimy „wersja” interfejsu API, brzmi to tak, że musi to również oznaczać wersję obiektów API, a także zapewnienie duplikatów wywołań metod dla każdej zmienionej sygnatury metody. Więc miałbym wtedy zwykły stary obiekt clr dla każdej wersji mojego interfejsu API, co znów wydaje się niepożądane. I nawet jeśli to zrobię, na pewno nie będę budował każdego obiektu od zera, ponieważ skończyłoby się to ogromną ilością powielonego kodu. Raczej API może rozszerzyć prywatne obiekty, których używamy do naszego podstawowego API, ale potem napotykamy ten sam problem, ponieważ dodane właściwości byłyby również dostępne w publicznym API, gdy nie powinny.
Jaki jest zatem rozsądek, który zwykle stosuje się w tej sytuacji? Wiem, że wiele usług publicznych, takich jak Git dla Windows, utrzymuje wersjonowany interfejs API, ale mam problem z wyobrażeniem sobie architektury, która to obsługuje bez ogromnej ilości zduplikowanego kodu obejmującego różne metody wersjonowane i obiekty wejścia / wyjścia.
Zdaję sobie sprawę, że procesy takie jak semantyczna wersja wersjonowania próbują wprowadzić rozsądek, kiedy powinny wystąpić przerwy w publicznym API. Problem polega na tym, że wydaje się, że wiele lub większość zmian wymaga zepsucia publicznego interfejsu API, jeśli obiekty nie są bardziej oddzielone, ale nie widzę dobrego sposobu na zrobienie tego bez powielania kodu.
źródło
I don't see a good way to do that without duplicating code
- Twój nowy interfejs API zawsze może wywoływać metody w starym interfejsie API lub odwrotnie.Odpowiedzi:
Podczas utrzymywania interfejsu API, który jest używany przez strony trzecie, nieuniknione jest, że będziesz musiał wprowadzić zmiany. Poziom złożoności będzie zależeć od rodzaju zachodzących zmian. Oto główne pojawiające się scenariusze:
Nowa funkcjonalność dodająca do istniejącego API
To najłatwiejszy scenariusz do wsparcia. Dodanie nowych metod do interfejsu API nie powinno wymagać żadnych zmian w istniejących klientach. Można to bezpiecznie wdrożyć dla klientów, którzy potrzebują nowej funkcjonalności, ponieważ nie ma żadnych aktualizacji dla żadnego istniejącego klienta.
Stara funkcjonalność wycofana z interfejsu API
W tym scenariuszu musisz poinformować istniejących użytkowników interfejsu API, że ta funkcjonalność nie będzie obsługiwana w dłuższej perspektywie. Dopóki nie zrezygnujesz z obsługi starej funkcjonalności (lub dopóki wszyscy klienci nie zostaną zaktualizowani do nowej funkcjonalności), musisz zachować starą i nową funkcjonalność API w tym samym czasie. Jeśli jest to biblioteka pod warunkiem, że większość języków ma sposób na oznaczenie starych metod jako przestarzałe / przestarzałe. Jeśli jest to jakaś usługa strony trzeciej, zwykle najlepiej jest mieć różne punkty końcowe dla starej / nowej funkcjonalności.
Istniejąca funkcjonalność w API zmienia się w jakiś sposób
Ten scenariusz będzie zależeć od rodzaju zmiany. Jeśli parametry wejściowe nie muszą być już używane, możesz po prostu zaktualizować usługę / bibliotekę, aby zignorować teraz dodatkowe dane. W bibliotece byłoby przeciążone wewnętrznie wywołanie metody nowej metody, która wymaga mniej parametrów. W usłudze hostowanej punkt końcowy ignoruje dodatkowe dane i może obsługiwać oba typy klientów i uruchamiać tę samą logikę.
Jeśli istniejąca funkcjonalność wymaga dodania nowych wymaganych elementów, musisz mieć dwa punkty końcowe / metody dla swojej usługi / biblioteki. Do czasu aktualizacji klientów musisz obsługiwać obie wersje.
inne przemyślenia
Rather, the API is likely to extend the private objects we are using for our base API, but then we run into the same problem because added properties would also be available in the public API when they are not supposed to be.
Nie narażaj wewnętrznych prywatnych obiektów za pośrednictwem biblioteki / usługi. Twórz własne typy i mapuj wewnętrzną implementację. Umożliwi to dokonanie zmian wewnętrznych i zminimalizowanie liczby aktualizacji wymaganych przez klientów zewnętrznych.
The problem is more that it seems like many or most changes require breaking the public API if the objects aren't more separated, but I don't see a good way to do that without duplicating code.
Interfejs API, niezależnie od tego, czy jest usługą, czy biblioteką, musi być stabilny w punkcie integracji z klientami. Im więcej czasu poświęcisz na określenie, jakie powinny być dane wejściowe i wyjściowe, i zachowaj je, ponieważ oddzielne jednostki pozwolą ci zaoszczędzić wielu problemów na drodze. Ustaw interfejs API jako osobny byt i zamapuj na klasy, które zapewniają prawdziwą pracę. Czas zaoszczędzony na zmianie wewnętrznych implementacji powinien bardziej zrównoważyć dodatkowy czas potrzebny na zdefiniowanie dodatkowego interfejsu.
Nie traktuj tego kroku jako „powielającego się kodu”. Choć są podobne, są to oddzielne byty, które warto poświęcić chwilę. Podczas gdy zmiany zewnętrznego interfejsu API prawie zawsze wymagają odpowiedniej zmiany wewnętrznej implementacji, zmiany wewnętrznej implementacji nie zawsze powinny zmieniać zewnętrzny interfejs API.
Przykład
Załóżmy, że udostępniasz rozwiązanie do przetwarzania płatności. Korzystasz z PaymentProviderA do przeprowadzania transakcji kartą kredytową. Później uzyskasz lepszą stawkę dzięki procesorowi płatności PaymentProviderB. Jeśli w interfejsie API widoczne są pola Adres karty kredytowej / rachunku zamiast typu PaymentProviderA, zmiana interfejsu API wynosi 0, ponieważ interfejs pozostał taki sam (mam nadzieję, że mimo to, jeśli PaymentProviderB wymaga danych, które nie były wymagane przez PaymentProviderA, należy wybrać jedną z opcji: wspierać oba lub utrzymać gorszą stawkę z PaymentProviderA).
źródło