Czy interfejs API REST może zwrócić wiele zasobów jako jeden zasób złożony?

10

Jestem w trakcie tworzenia interfejsu API REST i obecnie napotykam następujący problem:

  • Foojest pierwszym zasobem. Operacje CRUD można zastosować za pomocą /foo/identyfikatora URI.
  • Barjest drugim zasobem. Operacje CRUD można zastosować za pomocą /bar/identyfikatora URI.
  • Każdy Foojest powiązany z zerem lub jednym Bar. Powodem, dla którego nie traktuję tego Barjako podrzędnego źródła, Foojest to, że ta sama Barinstancja może być współużytkowana przez wiele różnych Foo. Pomyślałem, że lepiej jest uzyskać do niego dostęp za pośrednictwem niezależnego identyfikatora URI zamiast /foo/[id]/bar.

Mój problem polega na tym, że w znacznej liczbie przypadków klienci, którzy pytają o Fooinstancję, są również zainteresowani powiązaną Barinstancją. Obecnie oznacza to, że muszą wykonać dwa zapytania zamiast jednego. Chcę wprowadzić sposób, który pozwala uzyskać oba obiekty za pomocą jednego zapytania, ale nie wiem, jak modelować interfejs API w tym celu. Co do tej pory wymyśliłem:

  • Mogłem wprowadzić parametr zapytania podobnego do tego: /foo/[id]?include_bar=true. Problem z tym podejściem polega na tym, że reprezentacja zasobów (np. Struktura JSON) odpowiedzi musiałaby wyglądać inaczej (np. Kontener taki jak { foo: ..., bar: ... }zamiast tylko serializacji Foo), co powoduje, że Foopunkt końcowy zasobu jest „heterogeniczny”. Nie sądzę, że to dobra rzecz. Podczas wysyłania zapytań /fooklienci powinni zawsze uzyskać tę samą reprezentację zasobów (strukturę), niezależnie od parametrów zapytania.
  • Innym pomysłem jest wprowadzenie nowego punktu końcowego tylko do odczytu, np /fooandbar/[foo-id]. W takim przypadku nie ma problemu ze zwróceniem takiej reprezentacji { foo: ..., bar: ... }, ponieważ wtedy jest to tylko „oficjalna” reprezentacja fooandbarzasobu. Jednak nie wiem, czy taki punkt końcowy pomocnika jest naprawdę ODPOCZYNYWNY (dlatego napisałem „może” w tytule pytania. Oczywiście jest to technicznie możliwe, ale nie wiem, czy to dobry pomysł).

Co myślisz? Czy są jakieś inne możliwości?

ceran
źródło
Jaki jest termin relacji między Foo a Barem? Czy możesz powiedzieć, że Bar jest rodzicem Foo?
Nathan Merrill,
Nie Barmoże istnieć bez powiązania z Foo. Jednak, jak napisałem powyżej, możliwe jest, że wiele Foos dzieli to samo Bar. Powinno być możliwe stworzenie Foobez Barskojarzenia, więc nie sądzę, że Barpowinienem być traktowany jak rodzic.
ceran
1
Myślę, że napotykasz niektóre problemy, które miałem, tłumacząc bezpośrednio relacje modelu domeny na identyfikatory URI i zrównując zasoby z jednostkami domeny . Może to zainteresować interfejsy API REST muszą być sterowane hipertekstem . Szczególna uwaga na czwarty punkt
Laiv

Odpowiedzi:

6

Poziom 3 REST API wrócą ci Foo, a także odnośnik wskazujący związane Bar.

GET /foo/123
<foo id="123">
  ..foo stuff..
  <link rel="bar" uri="/bar/456"/>
</foo>

Następnie możesz dodać funkcję API do drążenia w dół, która umożliwia nawigację po linkach;

GET /foo/123?drilldown=bar
<foo id="123">
  ..foo stuff..
  <link rel="bar" uri="/bar/456">
    <bar id="456">
      ..bar stuff...
    </bar>
  </link>
</foo>

Funkcja drążenia w dół byłaby umieszczona przed interfejsami API i przechwytywała odpowiedzi. Wykonałby drążenie w dół połączeń i uzupełniał dane przed odesłaniem odpowiedzi dzwoniącemu.

Jest to dość powszechna rzecz na REST poziomu 3, ponieważ znacznie zmniejsza dyskontowanie klientów / serwerów w porównaniu z wolnym HTTP. Firma, dla której pracuję, tworzy interfejs API REST poziomu 3 z dokładnie tą funkcją.

Aktualizacja: Jaka jest jego wartość, oto jak może wyglądać w JSON. W ten sposób nasz interfejs API miałby taką strukturę. Pamiętaj, że możesz zagnieżdżać swoje wiercenia, aby wyciągać linki z linków itp.

GET /foo/123?drilldown=bar

{
  "self": {
    "type": "thing.foo",
    "uri": "/foo/123=?drilldown=bar",
    "href": "http://localhost/api/foo/123?drilldown=bar"
  },
  "links": [
    {
      "rel": "bar",
      "rev": "foo",
      "type": "thing.bar",
      "uri": "/bar/456",
      "href": "http://localhost/api/bar/456"
    }
  ],
  "_bar": [
    {
      "self": {
        "type": "thing.bar",
        "uri": "/bar/456",
        "href": "http://localhost/api/bar/456"
      },
      "links": [
        {
          ..other link..
        },
        {
          ..other link..
        }
      ]
    }
  ]
}
Qwerky
źródło
Co ciekawe, już używam hipermedialnych linków / kontrolek, aby usunąć ścisłe powiązanie z URI, ale nie myślałem o idei „drążenia w dół”, która wydaje się bardzo obiecująca. Jak mógł reprezentacja wygląd JSON jak? W tej chwili każda reprezentacja JSON z moich zasobów zawiera linkstablicę, każdy wpis jest obiektem związek z reli uripole (podobny do przykładu xml). Czy powinienem po prostu dodać trzecie pole do każdego obiektu łącza (np. data)? Czy jest jakiś standard?
ceran
Drążenie w dół nie jest tak naprawdę funkcją odpoczynku, więc nie ma żadnych standardów (przynajmniej o których wiem).
Qwerky,
Istnieje kilka proponowanych standardów, takich jak stateless.co/hal_specification.html, których używam w naszych aplikacjach. To bardzo blisko twojego przykładu.
Pete Kirkham,
4

W przypadku 95% wszystkich zapytań chcą Foojak również Bar, po prostu zwrócić go wewnątrz tego Fooobiektu podczas żądania Foo. Po prostu dodaj właściwość bar(lub inny termin dla relacji) i umieść Bartam obiekt. Jeśli relacja nie istnieje, użyj wartości null.

Myślę, że to przesadzasz :)

Nathan Merrill
źródło
Nie powinienem wymyślać tej liczby (95%), to był błąd, przepraszam. Chciałem powiedzieć, że duża część zapytań jest jednocześnie zainteresowana obydwoma zasobami. Ale nadal istnieje odpowiednia liczba żądań, które są tylko zainteresowane Foo, a ponieważ każda z nich Barma dość dużą pamięć (około 3x-4x wielkości Foo), nie chcę zwracać, Barjeśli klient nie zażąda jej wprost.
ceran
Jak duży rozmawiamy? Wątpię, że to będzie zrobić , że wielkiej różnicy w czasie transferu, a ja wolę czystego API nad prędkością
Nathan Merrill