RESTful API如何一次完成多個相依請求

剛才收到一封信,是一個有關RESTful API設計的問題。內容如下:

若目前我有三個Resource(R1,R2,R3),執行的順序是R1->R2->R3 ,而希望能達成R1做完的結果傳給R2,再接著R2做完的結果給R3,最後R3完成再做response,而不是client先向R1 request 得到response,再接著將response結果向R2 request 得到 response,而這樣依序下去。若希望達成這樣的目的,在Restful web service的架構中,該如何建立、執行,才能達成?

因為這個問題有一定的代表性,所以在這裡回答,可能對更多人有幫助。

基本上,希望一次呼叫就能在伺服器端完成多個有前後相依性的請求,是為了節省client/server來回傳遞的成本。假設一開始我們有三種資源API:

  • R1是/authors/:id,回傳結果的欄位articleIds包括作者所有文章的id
  • R2是/articles/:id,回傳結果的欄位pictureIds包括文章所有圖片的id
  • R3是/pictures/:id,回傳結果的欄位url為圖片網址

現在,假設我們想要得到某位作者任一篇文章的任一張圖片的網址(實務上會有更複雜的條件需求,在此省略以簡化討論)。直覺的做法,就是依序執行R1->R2->R3,分三次送request。

若是希望一次request/response就得到結果,我能想到的,至少有兩種解法:第一種比較通用,但不太符合RESTful API的精神;第二種要針對需求個別設計API,沒有通用性,但比較符合RESTful API精神。

1. 通用性的多請求Utility API

第一種做法,就是自己開一個multi-request API,比如就叫/multi-request,然後自己訂一些規則讓request body可以包含multiple requests,例如:

{
  "requests": [
    {"method": "GET", "uri": "/authors/1001"},
    {"method": "GET", "uri": "/articles/${responses[0].articleIds[0]}"},
    {"method": "GET", "uri": "/pictures/${responses[1].pictureIds[0]}"}
  ],
  "response": {
    "url": "${responses[2].url}"
  }
}

然後假設這個multi-request API在執行的時候,會正確替換request body中的${...} pattern。(這個語法是我隨意編的,只是為了討論舉例方便。)

2. 擴展Resource API的回傳內容格式定義

第二種做法,是針對常用的use case,擴展Resource API回傳內容。例如我們假設以下這個request,會在server端擴展articles資源,在回傳結果帶articles欄位,其欄位內容為該作者某一篇文章:

GET /authors/1001;expand:articles,limit:1

而以下這個request,會在server端將擴展後的articles資源裡的pictures資源再進一步擴展,在回傳結果的articles欄位中,帶pictures欄位,其欄位內容為該文章的某一張圖片:

GET /authors/1001;expand:articles,limit:1;expand:pictures,limit:1

除了用分號,也可以用一般的query string ?...&...。(這個語法也是我隨意編的,只是為了討論舉例。)真正設計API的時候,應該考慮現在及將來各種可能的use case,做出合理且具擴展性的設計決定。

希望以上的說明,有回答到問題。

4筆討論 回應文章

請問一下,若與原問題一樣的執行順序,R1與R2同在一個server1,而R3在server2,可否達成client端只傳送執行順序與request R1所需input給server1,由server1來對R1執行request並將response當作R2的request,server1得到R2的response後,知道要將response與執行順序交給server2,server2執行R3,因為知道執行順序已經結束,最後server2將R3的response回傳給client。 若之後改變順序為R1->R4->R5->R2,還是可以達成,而不是變成只固定能執行R1->R2->R3。謝謝

這是指用第一種解法的情況吧?可以擴充multi-request API的格式,接受帶host的request,例如:

{
  "requests": [
    {"method": "GET", "uri": "/authors/1001"},
    {"method": "GET", "uri": "/articles/${responses[0].articleIds[0]}"},
    {"method": "GET", "uri": "/pictures/${responses[1].pictureIds[0]}", "host": "api2.example.com"}
  ],
  "response": {
    "url": "${responses[2].url}"
  }
}

以上仍由server1負責將結果回傳給client。(由於TCP/IP是兩台機器之間的封包傳送,發給server1的請求,實務上不可能變成由server2回傳結果。)

Sam Lee5年(更新)

謝謝回答,是否有可能設計此執行plan的格式,例如用JSON設計,讓server端各自去parse,而知道執行完給哪個server,而不用特別設計這個multi-request API去執行
而若有多個相依的request,是否要針對每一個都設計一個multi-request? 還是有更彈性的作法? 能建立一個動態調整的multi-request
謝謝

是否有可能設計此執行plan的格式,例如用JSON設計,讓server端各自去parse,而知道執行完給哪個server,而不用特別設計這個multi-request API去執行

這已經超出RESTful API設計的範圍了,屬於系統架構設計的問題。因為不同系統的需求差異很大,從以上簡略的敘述,我很難給甚麼具體建議。

而若有多個相依的request,是否要針對每一個都設計一個multi-request? 還是有更彈性的作法? 能建立一個動態調整的multi-request

我在文章中提供的multi-request範例,就是採用彈性的設計,client發multi-request請求的時候,可以自行調整requests順序及相依關係(當然server端程式也要能正確parse才行)。