Projektowanie interfejsu API: podejście konkretne vs. abstrakcyjne - najlepsze praktyki?

25

Podczas omawiania interfejsów API między systemami (na poziomie biznesowym) w naszym zespole często występują dwa różne punkty widzenia: niektórzy wolą bardziej - powiedzmy - ogólne podejście abstrakcyjne, a inne proste „konkretne” podejście.

Przykład: projekt prostego interfejsu API „wyszukiwania osoby”. konkretna wersja byłaby

 searchPerson(String name, boolean soundEx,
              String firstName, boolean soundEx,
              String dateOfBirth)

Ludzie opowiadający się za konkretną wersją mówią:

  • interfejs API jest samodokumentujący
  • łatwo to zrozumieć
  • łatwa do sprawdzenia (kompilator lub jako usługa internetowa: sprawdzanie poprawności schematu)
  • POCAŁUNEK

Druga grupa ludzi w naszym zespole powiedziałaby „To tylko lista kryteriów wyszukiwania”

searchPerson(List<SearchCriteria> criteria)

z

SearchCritera {
  String parameter,
  String value,
  Map<String, String> options
}

z możliwym utworzeniem „parametru” jakiegoś typu wyliczenia.

Zwolennicy mówią:

  • bez zmiany (deklaracji) interfejsu API implementacja może się zmienić, np. dodając więcej kryteriów lub więcej opcji. Nawet bez synchronizacji takiej zmiany w czasie wdrażania.
  • dokumentacja jest konieczna nawet w przypadku konkretnego wariantu
  • sprawdzanie poprawności schematu jest przereklamowane, często trzeba dokonać dalszej weryfikacji, schemat nie obsługuje wszystkich przypadków
  • mamy już podobny interfejs API z innym systemem - ponowne użycie

Kontrargumentem jest

  • dużo dokumentacji na temat prawidłowych parametrów i prawidłowych kombinacji parametrów
  • większy wysiłek komunikacyjny, ponieważ trudniej jest zrozumieć dla innych zespołów

Czy są jakieś najlepsze praktyki? Literatura?

erik
źródło
3
powtórzone „String name / name, boolean soundEx” jest wyraźnym naruszeniem „ suchego” i sugeruje, że ten projekt nie uwzględnił faktu, że nazwa powinna pasować do soundEx. W obliczu takich prostych błędów projektowych trudno jest przeprowadzić bardziej wyrafinowane analizy
gnat
Przeciwieństwo „betonu” nie jest „ogólne”, lecz „abstrakcyjne”. Abstrakcja jest bardzo ważna dla biblioteki lub API, a ta dyskusja nie zadaje prawdziwie fundamentalnego pytania, zamiast tego skupia się na tym, co szczerze mówiąc, jest raczej trywialnym pytaniem o styl. FWIW, kontrargumenty dla opcji B brzmią jak ładunek FUD, nie powinieneś potrzebować żadnej dodatkowej dokumentacji ani komunikacji, jeśli projekt API jest nawet w połowie czysty i zgodny z zasadami SOLID.
Aaronaught
@Aaronaught dzięki za zwrócenie na to uwagi („streszczenie”). Może to być problem z tłumaczeniem, „generisch” w języku niemieckim nadal wydaje mi się OK. Jakie jest dla ciebie „naprawdę fundamentalne pytanie”?
erik
4
@Aaronaught: Pytanie nie dotyczy abstrakcji. Prawidłowa korekta byłaby przeciwna do „ogólnej” jest „specyficzna”, a nie „konkretna”.
Jan Hudec
Kolejny głos na to nie jest ogólny, a abstrakcyjny, lecz ogólny i szczegółowy. „Konkretny” powyższy przykład jest właściwie specyficzny dla nazwy, imienia i datyObrazu, drugi przykład jest ogólny dla dowolnych parametrów. Żadne z nich nie są szczególnie abstrakcyjne.
Zredagowałbym

Odpowiedzi:

18

To zależy od tego, ile pól mówisz i jak są one używane. Beton jest preferowany w przypadku wysoce ustrukturyzowanych zapytań zawierających tylko kilka pól, ale jeśli zapytania mają zazwyczaj bardzo swobodną formę, wówczas konkretne podejście szybko staje się niewygodne z więcej niż trzema lub czterema polami.

Z drugiej strony bardzo trudno jest utrzymać ogólny interfejs API w czystości. Jeśli wykonasz proste wyszukiwanie nazw w wielu miejscach, w końcu ktoś będzie się męczył powtarzaniem tych samych pięciu wierszy kodu i zawinie go w funkcję. Taki interfejs API niezmiennie ewoluuje w hybrydę ogólnego zapytania wraz z konkretnymi opakowaniami dla najczęściej używanych zapytań. I nie widzę w tym nic złego. Daje to, co najlepsze z obu światów.

Karl Bielefeldt
źródło
7

Projektowanie dobrego API to sztuka. Dobre API jest doceniane nawet po upływie czasu. Moim zdaniem nie powinno być ogólnego nastawienia na abstrakcyjno-konkretnej linii. Niektóre parametry mogą być tak konkretne, jak dni tygodnia, niektóre wymagają zaprojektowania pod kątem rozszerzalności (i jest dość głupie, aby były konkretne, np. Część nazw funkcji), jeszcze inne mogą pójść jeszcze dalej i aby mieć elegancki Interfejs API, który należy udostępnić, może nawet wywoływać połączenia zwrotne, a nawet język specyficzny dla domeny.

Rzadko zdarzają się nowe rzeczy pod Księżycem. Spójrz na stan techniki, szczególnie ustalone standardy i formaty (np. Wiele rzeczy można modelować po kanałach, opisy zdarzeń zostały opracowane w ical / vcal). Uczyń swój interfejs API łatwym do dodawania, gdy częste i wszechobecne byty są konkretne, a przewidywane rozszerzenia to słowniki. Istnieją również pewne ugruntowane wzorce postępowania w określonych sytuacjach. Na przykład obsługa żądania HTTP (i podobnego) może być modelowana w interfejsie API za pomocą obiektów Request i Response.

Przed zaprojektowaniem interfejsu API przeprowadź burzę mózgów na temat aspektów, w tym tych, które nie zostaną uwzględnione, ale musisz o tym wiedzieć. Przykładami takich są język, kierunek pisania, kodowanie, ustawienia regionalne, informacje o strefie czasowej i tym podobne. Zwróć uwagę na miejsca, w których mogą występować wielokrotności: użyj listy, a nie pojedynczej wartości dla nich. Na przykład, jeśli planujesz API dla systemu wideo, twój API będzie znacznie bardziej użyteczny, jeśli przyjmiesz N uczestników, a nie tylko dwóch (nawet jeśli Twoje specyfikacje są w tej chwili).

Czasami bycie abstrakcyjnym pomaga drastycznie zmniejszyć złożoność: nawet jeśli zaprojektujesz kalkulator do dodawania tylko 3 + 4, 2 + 2 i 7 + 6, wdrożenie X + Y może być znacznie prostsze (z technicznie wykonalnymi granicami dla X i Y i dołącz ADD (X, Y) do interfejsu API zamiast ADD_3_4 (), ADD_2_2 (), ...

Podsumowując, wybór w ten czy inny sposób jest tylko szczegółem technicznym. Twoja dokumentacja powinna opisywać przypadki częstego użytkowania w konkretny sposób.

Cokolwiek zrobisz po stronie struktury danych, podaj pole dla wersji interfejsu API.

Podsumowując, interfejs API powinien minimalizować złożoność podczas obsługi oprogramowania. Aby docenić API, poziom ujawnionej złożoności powinien być odpowiedni. Wybór formy interfejsu API zależy w dużej mierze od stabilności domeny problemowej. Dlatego należy oszacować, w jakim kierunku będzie rosło oprogramowanie i jego interfejs API, ponieważ informacje te mogą wpływać na równanie złożoności. Również projektowanie interfejsu API jest dostępne dla ludzi do zrozumienia. Jeśli istnieją jakieś dobre tradycje w dziedzinie technologii oprogramowania, w której się znajdujesz, staraj się nie odbiegać od nich zbytnio, ponieważ pomoże to zrozumieć. Weź pod uwagę, dla którego piszesz. Bardziej zaawansowani użytkownicy docenią ogólność i elastyczność, a ci z mniejszym doświadczeniem mogą czuć się bardziej komfortowo z konkretami. Jednak dbaj o większość użytkowników interfejsu API,

Jeśli chodzi o literaturę, mogę polecić wiodącym programistom „Piękny kod”, wyjaśniającym, jak myślą Andy Oram, Greg Wilson, ponieważ uważam, że piękno polega na postrzeganiu ukrytej optymalności (i przydatności do określonego celu).

Roman Susi
źródło
1

Moją osobistą preferencją jest abstrahowanie, ale zasady mojej firmy blokują mi konkretność. To dla mnie koniec debaty :)

Zrobiłeś dobrą robotę, wymieniając zalety i wady obu podejść, a jeśli będziesz dalej kopać, znajdziesz mnóstwo argumentów na korzyść obu stron. Tak długo, jak architektura interfejsu API jest prawidłowo opracowana - co oznacza, że ​​zastanawiałeś się, w jaki sposób będzie on używany dzisiaj i jak może ewoluować i rozwijać się w przyszłości - to i tak powinno być dobrze.

Oto dwie zakładki, które miałem z przeciwnymi punktami widzenia:

Preferowanie klas abstrakcyjnych

Preferowanie interfejsów

Zadaj sobie pytanie: „Czy interfejs API spełnia moje wymagania biznesowe? Czy mam dobrze zdefiniowane kryteria sukcesu? Czy można go skalować?”. Wydaje się, że są to proste proste praktyki, ale szczerze mówiąc, są one o wiele ważniejsze niż konkretne kontra ogólne.

gws2
źródło
1

Nie powiedziałbym, że abstrakcyjne API jest trudniejsze do zweryfikowania. Jeśli parametry kryteriów są wystarczająco proste i mają niewielkie zależności między sobą, nie ma znaczenia, czy parametry są przekazywane osobno, czy w tablicy. Nadal musisz je wszystkie zweryfikować. Ale to zależy od projektu parametrów kryteriów i samych obiektów.

Jeśli interfejs API jest wystarczająco złożony, posiadanie konkretnych metod nie jest opcją. W pewnym momencie prawdopodobnie skończysz z metodami o zbyt dużej liczbie parametrów lub zbyt wieloma prostymi metodami, które nie będą obejmować wszystkich wymaganych przypadków użycia. Jeśli chodzi o moje osobiste doświadczenie w projektowaniu zużywających się interfejsów API, lepiej jest mieć bardziej ogólne metody na poziomie interfejsu API i implementować określone wymagane opakowania na poziomie aplikacji.

Pavels
źródło
1

Argument zmiany należy odrzucić za pomocą YAGNI. Zasadniczo, chyba że faktycznie masz co najmniej 3 różne przypadki użycia, które używają ogólnego interfejsu API w inny sposób, szanse są dość niskie, projektujesz go, aby nie musiał się zmieniać, gdy pojawi się następny przypadek użycia (i gdy masz użycie przypadki, oczywiście potrzebujesz ogólnego interfejsu, kropki). Więc nie próbuj i bądź gotowy na zmianę.

W obu przypadkach zmiana nie musi być synchronizowana w celu wdrożenia. Kiedy uogólniasz interfejs później, zawsze możesz zapewnić bardziej szczegółowy interfejs dla kompatybilności wstecznej. Ale w praktyce każde wdrożenie będzie miało tak wiele zmian, że i tak je zsynchronizujesz, więc nie będziesz musiał testować stanów pośrednich. Nie uważałbym tego również za argument.

Jeśli chodzi o dokumentację, każde rozwiązanie może być łatwe w użyciu i oczywiste. Ale jest to ważny argument. Zaimplementuj interfejs, aby był łatwy w użyciu w rzeczywistych przypadkach. Czasami konkretne mogą być lepsze, a czasem ogólne.

Jan Hudec
źródło
1

Wolę podejście oparte na abstrakcyjnym interfejsie. Wysłanie zapytania do tego rodzaju usługi (wyszukiwania) jest częstym problemem i prawdopodobnie wystąpi ponownie. Ponadto prawdopodobnie znajdziesz więcej kandydatów do obsługi, którzy nadają się do ponownego użycia bardziej ogólnego interfejsu. Aby móc zapewnić spójny wspólny interfejs dla tych usług, nie wyliczyłem obecnie zidentyfikowanych parametrów zapytania w definicji interfejsu.

Jak już wcześniej wspomniano - podoba mi się możliwość zmiany lub rozszerzenia implementacji bez modyfikowania interfejsu. Dodanie kolejnych kryteriów wyszukiwania nie musi być odzwierciedlone w definicji usługi.

Chociaż zaprojektowanie dobrze zdefiniowanych, zwięzłych i ekspresyjnych interfejsów nie jest żadnym problemem, zawsze będziesz musiał dodatkowo dostarczyć dokumentację. Dodanie zakresu definicji dla prawidłowych kryteriów wyszukiwania nie stanowi takiego obciążenia.

0x0me
źródło
1

Najlepsze podsumowanie, jakie kiedykolwiek widziałem, to skala Rusty'ego, teraz nazywana manifestem Rusty API Design . Mogę tylko mocno to polecić. Dla kompletności przytaczam podsumowanie skali z pierwszego linku (im lepiej u góry, tym gorzej poniżej):

Dobre API

  • Nie można się pomylić.
  • Kompilator / linker nie pozwoli ci się pomylić.
  • Kompilator będzie ostrzegał, jeśli popełnisz błąd.
  • Oczywistym zastosowaniem jest (prawdopodobnie) właściwy.
  • Nazwa mówi, jak z niego korzystać.
  • Zrób to dobrze, bo zawsze się zepsuje w czasie wykonywania.
  • Postępuj zgodnie ze wspólną konwencją, a dobrze to zrozumiesz.
  • Przeczytaj dokumentację, a otrzymasz ją poprawnie.
  • Przeczytaj implementację, a zrozumiesz.
  • Przeczytaj prawidłowy wątek na liście mailowej, a zrozumiesz.

Złe interfejsy API

  • Przeczytaj wątek listy mailowej, a zrozumiesz go źle.
  • Przeczytaj implementację, a pomylisz się.
  • Przeczytaj dokumentację, a pomylisz się.
  • Postępuj zgodnie ze wspólną konwencją, a źle to zrozumiesz.
  • Zrób to dobrze, a czasem się zepsuje w czasie wykonywania.
  • Nazwa mówi, jak go nie używać.
  • Oczywiste użycie jest złe.
  • Kompilator ostrzeże, jeśli dobrze to zrobisz.
  • Kompilator / linker nie pozwoli ci tego zrobić poprawnie.
  • Nie da się dobrze zrobić.

Obie strony ze szczegółowymi informacjami tutaj i tutaj zawierają szczegółowe omówienie każdego punktu. Jest to naprawdę obowiązkowa lektura dla projektantów API. Dzięki Rusty, jeśli kiedykolwiek to przeczytasz.

JensG
źródło
0

Słowami laika:

  • Podejście abstrakcyjne ma tę zaletę, że pozwala zbudować wokół niego konkretne metody.
  • Odwrotna sytuacja nie jest prawdą
Tulains Córdova
źródło
Zaletą UDP jest możliwość budowania własnych niezawodnych strumieni. Dlaczego więc prawie wszyscy używają TCP?
svick,
Rozważa się także większość przypadków użycia. Niektóre przypadki mogą być potrzebne tak często, że można je uczynić wyjątkowymi.
Roman Susi
0

Jeśli rozszerzysz SearchCriteria pomysł trochę, może to daje elastyczność takich jak tworzenie AND, ORitp kryteria. Jeśli potrzebujesz takiej funkcjonalności, byłoby to lepsze podejście.

W przeciwnym razie zaprojektuj go pod kątem użyteczności. Ułatw interfejs API osobom, które go używają. Jeśli masz kilka podstawowych funkcji, które są często potrzebne (np. Wyszukiwanie osoby według jej nazwy), podaj je bezpośrednio. Jeśli zaawansowani użytkownicy potrzebują zaawansowanych wyszukiwań, mogą nadal korzystać z SearchCriteria.

Uooo
źródło
0

Co robi kod API? Jeśli jest to coś elastycznego, to elastyczny interfejs API jest dobry. Jeśli kod stojący za interfejsem API jest bardzo konkretny, to umieszczenie na nim elastycznej twarzy oznacza po prostu, że użytkownicy interfejsu API będą sfrustrowani i zirytowani tym, co udaje, że interfejs API jest możliwy, ale w rzeczywistości nie można go osiągnąć.

W przypadku Twojego wyszukiwania osoby wymagane są wszystkie trzy pola? Jeśli tak, to lista kryteriów jest zła, ponieważ pozwala na wiele zastosowań, które po prostu nie działają. Jeśli nie, wymaganie od użytkownika podania niepotrzebnych danych wejściowych jest złe. Jakie jest prawdopodobieństwo wyszukiwania według adresu w wersji V2? Elastyczny interfejs ułatwia dodawanie niż nieelastyczny.

Nie każdy system musi być bardzo elastyczny, starając się zrobić wszystko, podobnie jak architektura astronautyczna. Elastyczny łuk strzela strzałami. Elastyczny miecz jest równie przydatny jak gumowy kurczak.

kamieniste
źródło