Interfejs API REST - czy interfejs API powinien zwracać zagnieżdżone obiekty JSON?

38

Jeśli chodzi o interfejsy API JSON, czy dobrą praktyką jest spłaszczanie odpowiedzi i unikanie zagnieżdżonych obiektów JSON?

Jako przykład powiedzmy, że mamy interfejs API podobny do IMDb, ale do gier wideo. Istnieje kilka encji: Gra, Platforma, ESRBRating i GamePlatformMap, która mapuje Gry i platformy.

Powiedzmy, że żądasz / game / 1, która pobiera grę o identyfikatorze 1 i zwraca obiekt gry z zagnieżdżonymi platformami i esrbRating.

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
  "platforms": [
    {"id":1,"name":"Xbox"},
    {"id":2,"name":"Playstation"}
  ],
  "esrbRating": {
    "id": 1,
    "code": "E",
    "name": "Everyone"
  }
}

Jeśli używasz czegoś takiego jak JPA / Hibernate, może to zrobić automatycznie, jeśli jest ustawiony na FETCH.EAGER.

Inną opcją jest po prostu interfejs API i dodanie kolejnych punktów końcowych.

W takim przypadku, gdy wymagane jest / game / 1, zwracany jest tylko obiekt gry.

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
}

Jeśli chcesz platformy i / lub ESRBRating, musisz zadzwonić pod następujący numer:

/ game / 1 / platform / game / 1 / esrb

Wydaje się, że ta metoda może potencjalnie dodać kilka kolejnych wywołań do serwera, w zależności od danych potrzebnych klientowi i kiedy go potrzebuje.

Była jedna ostatnia myśl, w której chciałbym, żebyś wrócił coś takiego.

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
  "platforms": ["Xbox","Playstation"]
}

Zakłada się jednak, że nie potrzebują one identyfikatorów ani żadnych innych informacji powiązanych z tymi obiektami platformy.

Pytam ogólnie, jaki jest najlepszy sposób na uporządkowanie obiektów JSON zwróconych z interfejsu API. Czy powinieneś starać się być jak najbliżej swoich bytów, czy też dobrze jest używać Obiektów Domeny lub Obiektów Transferu Danych? Rozumiem, że metody będą miały kompromisy, albo więcej pracy na warstwie dostępu do danych, albo więcej pracy dla klienta.

Chciałbym również usłyszeć odpowiedź związaną z używaniem Spring MVC jako technologii zaplecza dla API, z JPA / Hibernate lub MyBatis dla trwałości.

greyfox
źródło
6
Jakie ewentualne zastrzeżenia zwracasz osadzonych obiektów? Zwracanie osadzonych obiektów indywidualnie z różnych punktów końcowych będzie cholernie denerwujące (nie wspominając o powolnym).
Robert Harvey
1
Osobiście nie mam żadnych zastrzeżeń. Po prostu nie jestem świadomy najlepszych praktyk. Kolega twierdzi, że praca z obiektami osadzonymi w AngularJS nie jest prosta i ostatecznie chciałbym, aby każda aplikacja Ember of AngularJS korzystała z interfejsu API. Nie wiem wystarczająco dużo o Angular lub Ember, aby wiedzieć, czy to będzie miało wpływ, czy nie.
greyfox,
3
Odpowiedź będzie zależeć od tego, czy chcesz zwrócić obiekty domeny, obiekty DTO, obiekty ViewModel czy obiekty KitchenSink. Który obiekt, który zwrócisz, najprawdopodobniej zostanie określony na podstawie potrzeb Twojej aplikacji i tego, jak ten obiekt zachowuje się w Internecie. Przykład: jeśli próbujesz wypełnić stronę internetową danymi z faktury, najprawdopodobniej zwrócisz obiekt zawierający wszystko, czego potrzebujesz (chyba że planujesz AJAXing w elementach zamówienia lub coś takiego).
Robert Harvey
Tak jest w przypadku, gdy poprosisz o grę, prawdopodobnie chcesz poznać gatunki, platformy i ESRBRating. To ma sens. Jeśli chodzi o projektowanie z perspektywy Java, czy poleciłbyś posiadanie pakietu Entity, który zawiera przystawki JPA, a następnie pakietu domeny, który to obiekty biznesowe / DTO powracają do użytkownika?
greyfox,
1
Połączenia z serwerem są drogie. Interfejs API, który wymaga wysyłania danych za pomocą wielu połączeń, będzie wolniejszy niż interfejs API, który pozwala uzyskać wszystko w jednym połączeniu, często nawet wtedy, gdy ten drugi zwraca niepotrzebne informacje.
Gort the Robot

Odpowiedzi:

11

Kolejna alternatywa (przy użyciu HATEOAS). Jest to proste, głównie w praktyce dodajesz znacznik links w pliku json, w zależności od korzystania z HATEOAS.

http://api.example.com/games/1:

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
  "platforms": [
    {"_self": "http://api.example.com/games/1/platforms/53", "name": "Playstation"},
    {"_self": "http://api.example.com/games/1/platforms/34", "name": "Xbox"},
  ]
}

http://api.example.com/games/1/platforms/34:

{
  "id": 34,
  "title": "Xbox",
  "publisher": "Microsoft",
  "releaseDate": "2015-01-01",
  "testReport": "http://api.example.com/games/1/platforms/34/reports/84848.pdf",
  "forms": [
    {"type": "edit", "fields: [] },
  ]
}

Oczywiście możesz osadzić wszystkie dane na wszystkich listach, ale prawdopodobnie będzie to o wiele za dużo danych. W ten sposób możesz osadzić wymagane dane, a następnie załadować więcej, jeśli naprawdę chcesz z nimi pracować.

Techniczna implementacja może zawierać buforowanie. Możesz buforować linki i nazwy platform w obiekcie gry i wysyłać je natychmiast, bez konieczności wczytywania interfejsu API platform. W razie potrzeby można go załadować.

Widzisz na przykład, że dodałem informacje o formularzu. Zrobiłem to, aby pokazać, że w szczegółowym obiekcie Json może być znacznie więcej informacji, niż chciałbyś wczytać na liście gier.

Luc Franken
źródło
Nie sądzę, żeby to technicznie HATEOS, ponieważ nie ma stanu.
RibaldEddie
Tak, nie jestem pewien dokładne słowo na temat tego procesu. HATEOS jest ogólnie używany do łączenia w pozostałych interfejsach API, ale zgadzam się, że ma to również związek ze stanem. Chociaż pomysł wdrożenia będzie taki sam. Oto trochę więcej o tym, jak można go użyć na przykładzie: stormpath.com/blog/linking-and-resource-expansion-rest-api-tips
Luc Franken
To jednak dobry pomysł!
RibaldEddie
1
Jeśli opracowujesz interfejs API, w którym istnieje spójność między klientem a samym interfejsem API (powiedzmy wewnętrzny interfejs API), sensowniejsze może być zwrócenie zagnieżdżonej (lub spłaszczonej) odpowiedzi niż podanie linków do innego zasobu, co oznacza więcej żądań interfejsu API które mogą być niepożądane.
Bruno,
@bruno tak, ale z ograniczeniem: w większych systemach nie można lub nie chcesz dostarczać wszystkich powiązanych obiektów w całości. Pola, które domyślnie podajesz, są dowolne, możesz je wybrać na podstawie użycia interfejsu API. Więc w tym przypadku możesz mieć platformy z setkami pól, przypadek użycia pokazuje pole wyboru do wyboru platformy. W takim przypadku warto podać nazwę platformy, ale nie potrzebuje ona na przykład szczegółów finansowych platformy.
Luc Franken
16

To jedno z podstawowych pytań dotyczących projektowania interfejsu API REST. Każdy projektant zadaje sobie to pytanie już pierwszego dnia. Przepraszamy, ale odpowiedź brzmi „to zależy”. Każde podejście ma zalety i wady, a Ty musisz tylko podjąć decyzję i iść z nią.

RibaldEddie
źródło
11
To wcale nie jest pomocne. Sam OP wiedział „to zależy, a każde podejście ma wady i zalety”. Powinieneś wyjaśnić, na czym to zależy, lub przynajmniej podać jakiś przykład.
Pratik Singhal
5

Opieram się na przedstawionym tutaj podejściu https://www.slideshare.net/stormpath/rest-jsonapis

Krótko mówiąc, włącz zasób zagnieżdżony jako łącza do zasobu nadrzędnego, tymczasem podaj parametr rozwijania w nadrzędnym punkcie końcowym.

Moim zdaniem jest to sposób, który jest skuteczny i elastyczny w większości przypadków.

Wei Qiu
źródło
2
Lubię to podejście. Każdy, kto się zastanawia, zaczyna się od SLIDE 57 w połączonym pokazie slajdów.
Adam Plocher