REST API - zbiorcze tworzenie lub aktualizowanie w pojedynczym żądaniu [zamknięte]

94

Załóżmy, że istnieją dwa zasoby, Bindera Docrelacja skojarzeniowa oznacza, że Doci Binderstoją samodzielnie. Docmoże lub nie należy do Binderi Bindermoże być pusty.

Jeśli chcę zaprojektować interfejs API REST, który umożliwia użytkownikowi wysyłanie kolekcji Docs, W JEDNYM ŻĄDANIU , jak poniżej:

{
  "docs": [
    {"doc_number": 1, "binder": 1}, 
    {"doc_number": 5, "binder": 8},
    {"doc_number": 6, "binder": 3}
  ]
}

Oraz dla każdego dokumentu w docs,

  • Jeśli docistnieje, przypisz go doBinder
  • Jeśli docnie istnieje, utwórz go, a następnie przypisz

Jestem naprawdę zdezorientowany, jak należy to zaimplementować:

  • Jakiej metody HTTP użyć?
  • Jaki kod odpowiedzi należy zwrócić?
  • Czy to w ogóle kwalifikuje się do REST?
  • Jak wyglądałby identyfikator URI? /binders/docs?
  • Obsługa żądań zbiorczych, co się stanie, jeśli kilka pozycji spowoduje błąd, a inne przejdą. Jaki kod odpowiedzi należy zwrócić? Czy operacja zbiorcza powinna być atomowa?
Sam R.
źródło

Odpowiedzi:

59

Myślę, że możesz użyć metody POST lub PATCH, aby sobie z tym poradzić, ponieważ zazwyczaj projektują do tego.

  • Użycie POSTmetody jest zwykle używane do dodawania elementu, gdy jest używane w zasobie listy, ale można również obsługiwać kilka akcji dla tej metody. Zobacz tę odpowiedź: Jak zaktualizować zbiór zasobów REST . Możesz także obsługiwać różne formaty reprezentacji danych wejściowych (jeśli odpowiadają one tablicy lub pojedynczym elementom).

    W takim przypadku nie jest konieczne definiowanie formatu, aby opisać aktualizację.

  • Użycie PATCHmetody jest również odpowiednie, ponieważ odpowiednie żądania odpowiadają częściowej aktualizacji. Według RFC5789 ( http://tools.ietf.org/html/rfc5789 ):

    Kilka aplikacji rozszerzających protokół Hypertext Transfer Protocol (HTTP) wymaga funkcji częściowej modyfikacji zasobów. Istniejąca metoda HTTP PUT umożliwia tylko całkowitą wymianę dokumentu. Ta propozycja dodaje nową metodę HTTP, PATCH, w celu zmodyfikowania istniejącego zasobu HTTP.

    W takim przypadku musisz zdefiniować swój format, aby opisać częściową aktualizację.

Myślę, że w tym przypadku POSTi PATCHsą dość podobne, ponieważ tak naprawdę nie musisz opisywać operacji do wykonania dla każdego elementu. Powiedziałbym, że zależy to od formatu przesłanego oświadczenia.

Sprawa PUTjest nieco mniej jasna. W rzeczywistości, używając metody PUT, powinieneś podać całą listę. W rzeczywistości reprezentacja przedstawiona w żądaniu będzie zastępować reprezentację zasobu listy.

Możesz mieć dwie opcje dotyczące ścieżek zasobów.

  • Korzystanie ze ścieżki zasobów dla listy dokumentów

W takim przypadku musisz wyraźnie podać link do dokumentów z segregatorem w reprezentacji podanej w żądaniu.

Oto przykładowa trasa do tego /docs.

Treść takiego podejścia może dotyczyć metody POST:

[
    { "doc_number": 1, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 2, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 3, "binder": 5, (other fields in the case of creation) },
    (...)
]
  • Używanie ścieżki zasobu podrzędnego elementu binder

Ponadto można również rozważyć wykorzystanie tras podrzędnych do opisu powiązania między dokumentami a segregatorami. Wskazówki dotyczące powiązania między dokumentem a segregatorem nie muszą być teraz określane w treści żądania.

Oto przykładowa trasa do tego /binder/{binderId}/docs. W takim przypadku wysyłanie listy dokumentów metodą POSTlub PATCHdołączanie dokumentów do segregatora z identyfikatorem binderIdpo utworzeniu dokumentu, jeśli nie istnieje.

Treść takiego podejścia może dotyczyć metody POST:

[
    { "doc_number": 1, (other fields in the case of creation) },
    { "doc_number": 2, (other fields in the case of creation) },
    { "doc_number": 3, (other fields in the case of creation) },
    (...)
]

Jeśli chodzi o odpowiedź, to do Ciebie należy określenie poziomu odpowiedzi i błędów do zwrócenia. Widzę dwa poziomy: poziom statusu (poziom globalny) i poziom ładunku (poziom cieńszy). Do Ciebie należy również określenie, czy wszystkie wstawki / aktualizacje odpowiadające Twojemu żądaniu muszą być atomowe, czy nie.

  • Atomowy

W takim przypadku możesz wykorzystać stan HTTP. Jeśli wszystko pójdzie dobrze, otrzymasz status 200. Jeśli nie, inny status, na przykład 400jeśli podane dane nie są poprawne (na przykład identyfikator segregatora jest nieprawidłowy) lub coś innego.

  • Nieatomowy

W takim przypadku 200zostanie zwrócony status i do reprezentacji odpowiedzi należy opisanie, co zostało zrobione i gdzie ostatecznie wystąpiły błędy. ElasticSearch ma punkt końcowy w swoim interfejsie API REST do zbiorczej aktualizacji. To może dać ci kilka pomysłów na tym poziomie: http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/bulk.html .

  • Asynchroniczny

Możesz również zaimplementować przetwarzanie asynchroniczne w celu obsługi dostarczonych danych. W takim przypadku zwracany stan HTTP będzie 202. Klient musi pobrać dodatkowe zasoby, aby zobaczyć, co się stanie.

Zanim skończę, chciałbym również zauważyć, że specyfikacja OData rozwiązuje problem dotyczący relacji między jednostkami z funkcją o nazwie linki nawigacyjne . Może mógłbyś rzucić okiem na to ;-)

Poniższy link może Ci również pomóc: https://templth.wordpress.com/2014/12/15/designing-a-web-api/ .

Mam nadzieję, że ci to pomoże, Thierry

Thierry Templier
źródło
Mam pytanie. Zdecydowałem się na płaskie trasy bez zagnieżdżonego zasobu podrzędnego. Aby uzyskać wszystkie dokumenty nazywam GET /docsi pobrać wszystkie dokumenty w szczególności spoiwa GET /docs?binder_id=x. Aby usunąć podzbiór zasobów, DELETE /docs?binder_id=xczy powinienem wywołać, czy powinienem wywołać DELETE /docsz {"binder_id": x}w treści żądania? Czy kiedykolwiek PATCH /docs?binder_id=xużywałbyś aktualizacji zbiorczej, czy po prostu PATCH /docsi przepuszczał pary?
Andy Fusniak
35

Prawdopodobnie będziesz musiał użyć POST lub PATCH, ponieważ jest mało prawdopodobne, aby pojedyncze żądanie, które aktualizuje i tworzy wiele zasobów, będzie idempotentne.

Robienie PATCH /docsto zdecydowanie ważna opcja. Używanie standardowych formatów poprawek może okazać się trudne w twoim konkretnym scenariuszu. Nie jestem tego pewien.

Możesz użyć 200. Możesz również użyć 207 - Multi Status

Można to zrobić w RESTful sposób. Moim zdaniem kluczem jest posiadanie zasobu przeznaczonego do akceptowania zestawu dokumentów do aktualizacji / utworzenia.

Jeśli używasz metody PATCH, myślę, że twoja operacja powinna być atomowa. tj. nie użyłbym kodu stanu 207, a następnie nie zgłosiłbym sukcesów i niepowodzeń w treści odpowiedzi. Jeśli używasz operacji POST, podejście 207 jest wykonalne. Będziesz musiał zaprojektować własne ciało odpowiedzi, aby komunikować, które operacje zakończyły się sukcesem, a które nie. Nie znam standardowego.

Darrel Miller
źródło
Dziękuję bardzo. Przez This can be done in a RESTful wayto znaczy Update i tworzyć muszą być wykonane oddzielnie?
Sam R.
1
@norbertpy Wykonywanie jakiejś operacji zapisu na zasobie może spowodować zaktualizowanie i utworzenie innych zasobów na podstawie pojedynczego żądania. REST nie ma z tym problemu. Wybrałem frazę, ponieważ niektóre platformy implementują operacje masowe poprzez serializowanie żądań HTTP do dokumentów wieloczęściowych, a następnie wysyłanie serializowanych żądań HTTP jako wsad. Myślę, że to podejście narusza ograniczenie REST identyfikacji zasobów.
Darrel Miller
19

PUT ing

PUT /binders/{id}/docs Utwórz lub zaktualizuj i powiąż pojedynczy dokument z segregatorem

na przykład:

PUT /binders/1/docs HTTP/1.1
{
  "docNumber" : 1
}

PATCH ing

PATCH /docs Utwórz dokumenty, jeśli ich nie ma, i powiąż je z segregatorami

na przykład:

PATCH /docs HTTP/1.1
[
    { "op" : "add", "path" : "/binder/1/docs", "value" : { "doc_number" : 1 } },
    { "op" : "add", "path" : "/binder/8/docs", "value" : { "doc_number" : 8 } },
    { "op" : "add", "path" : "/binder/3/docs", "value" : { "doc_number" : 6 } }
] 

Dodatkowe spostrzeżenia dołączę później, ale w międzyczasie, jeśli chcesz, spójrz na RFC 5789 , RFC 6902 i William Durand's Please. Wpis na blogu Don't Patch Like a Idiot .

Mauricio Morales
źródło
2
Czasami klient potrzebuje operacji zbiorczej i nie chce dbać o to, czy zasób istnieje, czy nie. Jak powiedziałem w pytaniu, klient chce wysłać kilka docsi skojarzyć z nimi binders. Klient chce utworzyć segregatory, jeśli nie istnieją, i utworzyć skojarzenie, jeśli tak. W JEDNYM POJEDYNCZYM ZAMÓWIENIU.
Sam R.
12

W projekcie, nad którym pracowałem, rozwiązaliśmy ten problem, wprowadzając coś, co nazywamy żądaniami „wsadowymi”. Zdefiniowaliśmy ścieżkę, w /batchktórej przyjęliśmy json w następującym formacie:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 5,
         binder: 8
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      }
   },
]

Odpowiedź ma kod stanu 207 (Multi-Status) i wygląda następująco:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
      status: 200
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         error: {
            msg: 'A document with doc_number 5 already exists'
            ...
         }
      },
      status: 409
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      },
      status: 200
   },
]

Możesz również dodać obsługę nagłówków w tej strukturze. Zaimplementowaliśmy coś, co okazało się przydatne, czyli zmienne do wykorzystania między żądaniami w partii, co oznacza, że ​​możemy użyć odpowiedzi z jednego żądania jako danych wejściowych do innego.

Facebook i Google mają podobne implementacje:
https://developers.google.com/gmail/api/guides/batch
https://developers.facebook.com/docs/graph-api/making-multiple-requests

Jeśli chcesz utworzyć lub zaktualizować zasób za pomocą tego samego wywołania, użyłbym POST lub PUT w zależności od przypadku. Jeśli dokument już istnieje, czy chcesz, aby cały dokument był:

  1. Zastąpiony dokumentem, który wysłałeś (tj. Brakujące właściwości w żądaniu zostaną usunięte, a już istniejące zostaną nadpisane)?
  2. Połączone z dokumentem, który wysyłasz (tj. Brakujące właściwości w żądaniu nie zostaną usunięte, a już istniejące właściwości zostaną nadpisane)?

Jeśli chcesz zachowanie z alternatywy 1, powinieneś użyć POST, a jeśli chcesz zachowanie z alternatywy 2, powinieneś użyć PUT.

http://restcookbook.com/HTTP%20Methods/put-vs-post/

Jak już sugerowali ludzie, możesz również wybrać PATCH, ale wolę zachować proste API i nie używać dodatkowych czasowników, jeśli nie są potrzebne.

David Berg
źródło
5
Polub tę odpowiedź na dowód koncepcji, a także linki do Google i Facebooka. Ale nie zgadzam się z końcową częścią o POST lub PUT. W 2 przypadkach, o których mowa w tej odpowiedzi, pierwszą z nich powinno być PUT, a drugą - PATCH.
RayLuo,
@RayLuo, czy możesz wyjaśnić, dlaczego potrzebujemy PATCH oprócz POST i PUT?
David Berg,
2
Ponieważ po to został wymyślony PATCH. Możesz przeczytać tę definicję i zobaczyć, jak PUT i PATCH pasują do twoich 2 punktorów.
RayLuo
@DavidBerg, Wygląda na to, że Google preferuje inne podejście do przetwarzania żądań wsadowych, tj. Oddzielenie nagłówka i treści każdego żądania podrzędnego do odpowiedniej części żądania głównego, z podobnymi granicami --batch_xxxx. Czy są jakieś istotne różnice między rozwiązaniami Google i Facebooka? Dodatkowo, jeśli chodzi o „wykorzystanie odpowiedzi z jednego żądania jako danych wejściowych do innego”, brzmi to bardzo interesująco, czy mógłbyś podzielić się więcej szczegółami? lub jakiego rodzaju scenariusza należy użyć?
Yang