Przygotowanie API – REST, czy nie do końca?

Pracę nad moim projektem zacząłem od zadania sobie pytania: jak powinna wyglądać architektura mojego programu? I chociaż na temat samej architektury dodam oddzielny post, potrzebne będzie krótkie wprowadzenie. Głównym założeniem jest powstanie aplikacji dostępnej z poziomu przeglądarki, dzięki czemu będzie można uruchomić ją na komputerach PC oraz Mac, tabletach, a nawet smartfonach. Wiadomo jednak, że na urządzeniach przenośnych dużo wygodniej korzysta się z aplikacji natywnych. Tak powstał kolejny cel, czyli apka na urządzenia zaopatrzone w system Android. Nie postanowiłem jeszcze jak będzie ona wyglądać, ale skłaniam się ku rozwiązaniu w którym będzie ona miała ten sam kod (w miarę możliwości) co strona www, dzięki zastosowaniu rozwiązań takich jak np. Adobe PhoneGap. Jeżeli tak się sprawy mają, to naturalnym wnioskiem dla mnie jest to, że muszę przygotować jakieś API, które mój klient będzie mógł wykorzystać. Tutaj pojawia się kolejne pytanie, na które chcę dzisiaj odpowiedzieć: czy moje API powinno być RESTful, czy też nie? Może wystarczy podejście RPC.

REST czy RPC? I co to takiego?

W celu podjęcia decyzji, postaram się w skrócie opisać te dwa podejścia i wskazać czym się różnią. Oba z tych rozwiązań w tym kontekście operować będą na protokole HTTP. Zapytanie przy użyciu tego protokołu składa się z między innymi:

  • Metody (Mehtod) np. GET, HEAD, POST, która decyduje o tym jaką operację wykonamy na zasobie
  • Nagłówków (Header), w których dostępne są dodatkowe informacje o żądaniu np. dane pozwalające na uwierzytelnianie
  • Ciała zapytania (Content body), które przechowuje informacje np. o zasobie który chcemy dodać/edytować

Niektóre z metod są idempotentne (Idempotent) (z założenia, tak powinny być zaimplementowane), to znaczy że nie ważne ile razy je wykonamy w danych warunkach, zawsze zwrócą tej sam wynik (np. GET i PUT). Inną cechą niektórych metod jest to że są bezpieczne (Safe), czyli nie modyfikują one danych i nie posiadają skutków ubocznych np. GET i HEAD (tak jak poprzednio, w ten sposób powinny być implementowane).

REST, czyli Representational State Transfer to architektura pozwalająca na pisanie skalowalnych usług internetowych. Nie będę rozpisywał się na temat definicji pojęcia REST. Opisując w skrócie: udostępniamy zasoby na których możemy wykonywać różne operacje za pomocą protokołu HTTP. Rodzaj operacji zależy od tego, jakiej metody użyjemy podczas zapytania. Przykładowo, poniższe trzy zapytania będą miały różne wyniki:

  • GET www.example.com/api/objects/2
  • DELETE www.example.com/api/objects/2
  • PUT www.example.com/api/objects/2

W przypadku, gdy mówimy o REST, mamy na myśli podejście zorientowane na zasoby. Definiując adres zasobu używamy rzeczowników, a to jaką akcję wykonujemy nie jest wskazywane przez dodanie czasownika, lecz przez wybór metody HTTP.

RPC (Remote procedure call) ma wiele definicji, ale w tym znaczeniu chodzi o podejście, w którym zamiast operować na zasobach, mamy do czynienia z operacjami. Operacje te w swojej nazwie sugerują co będzie efektem ich wykonania. W przypadku implementacji tego podejścia za pomocą protokołu HTTP wykorzystuje się głównie dwie metody: GET oraz POST. Nie ma tutaj jasno sformułowanych zasad, ale zazwyczaj GET służy do pobierania danych lub akcji, które nie zmieniają żadnych danych, natomiast POST służy do pozostałych operacji. Przykładowe wywołanie takich zapytań:

  • GET www.example.com/api/GetAllObjects
  • POST www.example.com/api/CreateObject

Jeżeli wiemy już jak takie zapytania wyglądają i czym się charakteryzują, możemy przystąpić do porównania. Wezmę pod uwagę kilka czynników, które moim zdaniem są ważne podczas wyboru metody implementacji naszego API.

Projektowanie

Na początek weźmy pod uwagę to, w jak łatwy sposób można projektować nasze API. Jeżeli weźmiemy pod uwagę systemy, które już istnieją np. mamy napisaną już bibliotekę i chcemy w jakiś sposób wyprowadzić ją na świat, łatwiej będzie nam użyć podejścia RPC. Dzieje się tak z reguły dla tego, że większość kodu jaki piszemy jest zorientowany na operacje i łatwiej go w ten sposób odwzorować. Łatwiej też będzie rozwijać system, który głównie opiera się o różnego rodzaju procesy, gdzie jeden obiekt ciągle zmienia się wg jakiejś logiki. Wadą tego podejścia może być to, że ponieważ nie nakłada nam ona żadnych ograniczeń, projektant musi sam pilnować tego, żeby API było spójne i składało się w logiczną całość. Z drugiej strony, REST ułatwia projektowanie prostszych systemów gdzie głównym zadaniem są operacje typu CRUD. Nawet, jeżeli projektowanie API RESTowego może być trudniejsze, dzięki założeniom jakie ze sobą niesie łatwiej otrzymać spójne i logiczne zaplecze dla naszej aplikacji. Tutaj pytanie pozostaje bez odpowiedzi moim zdaniem, ponieważ wszystko zależy od kontekstu w jakim będziemy pracować.

Dokumentacja i jak łatwo ją przygotować

W obu przypadkach możemy wykorzystać narzędzia typu Swagger do udokumentowania naszego API, więc żadne podejście nie jest tutaj lepsze. Sama nazwa zasobu/operacji także może nam sporo powiedzieć o tym, co będzie efektem wywołania danego zapytania. GET www.example.com/api/GetAllObjects czy GET www.example.com/api/objects w jasny sposób mówi nam, co dostaniemy w odpowiedzi, więc można tutaj powiedzieć w pewnym stopniu o tym jak API dokumentuje się samo. Z doświadczenia wiadomo jednak, że takie rzeczy nie zdarzają się zbyt często i trzeba posiłkować się dodatkowymi źródłami wiedzy o naszym requescie.

Semantyka i używalność

W RPC nie jesteśmy w stanie przewidzieć tego, jak dana operacja będzie się nazywać. Ta sama operacja może przybierać taki wygląd:

  • GET/POST www.example.com/api/RemoveObject/1
  • GET/POST www.example.com/api/DeleteObject/1

Zdajemy się tutaj na to, jaki styl wybiera osoba odpowiedzialna za przygotowanie API. Można założyć, że będzie się ona trzymała swojego standardu i podczas usuwania innego rodzaju obiektu użyje tej samej nazwy zapytania. Jednak może się zdarzyć, że za każdym razem trzeba będzie zagłębiać się w dokumentację. REST jest bardziej przewidywalny, dzięki wykorzystywaniu metod HTTP. Nawet, jeżeli nie wiemy dokładnie co stanie się po wykonaniu danej metody, mamy ogólne pojęcie co może być efektem końcowym. W tym przypadku, mój głos dostaje podejście REST, chociaż nie musi zawsze tak być.

Obsługa Cache

Obsługa Cache to jedna z bardzo ważnych rzeczy, które powinna zapewniać nasza RESTowa architektura. Patrząc na specyfikację metod HTTP w łatwy sposób dodać taką obsługę do naszego API. W podejściu RPC jest to dużo trudniejsze, zwłaszcza, gdy zbyt często będziemy korzystać z metody POST. Nie jest to jednak niemożliwe. W tym punkcie można skłaniać się minimalnie w stronę architektury REST.

Podsumowanie

Niektórzy stwierdzą, że wybór naszego podejścia można sprowadzić tylko do osobistych odczuć na temat tego, czy wolimy taki czy inny styl zapytań. Jednak od naszego podejścia w dużej mierze zależy to, w jaki sposób będziemy później pisać nasze aplikacje. Moim zdaniem, głównym czynnikiem jakim powinniśmy się kierować jest to, jaki jest nasz projekt i nie ma sensu od razu zakładać że zrobimy to tak, czy inaczej. Pomimo kilku punktów, w których REST jest lepszym podejściem, np. to że jest bardzo przejrzysty i przewidywalny, ma on też kilka sporych wad i nieudogodnień, które wynikają z jego ograniczeń. Dlatego ja zwykle stawiam na pragmatyzm w moich rozwiązaniach i czerpię to, co najlepsze z danych rozwiązań. Nawet, jeżeli moje API będzie bardziej skłaniało się ku architekturze REST, to nie będę bał się użyć podejścia bardziej skierowanego na operacje. Moim zdaniem POST www.example.com/api/game/1/endturn będzie lepiej opisywało sytuację, niż zmiana właściwości obiektu i wykonywanie konkretnej operacji później.

A jakie jest Twoje zdanie na ten temat? Wszelkie uwagi mile widziane! Zapraszam do komentowania i kolejny raz polecam się na Twitterze!