差異處

這裏顯示兩個版本的差異處。

連向這個比對檢視

Both sides previous revision 前次修改
下次修改
前次修改
java:web:restapi:http_method_cant_make_sense [2017/09/25 00:26]
tony [Introduction]
java:web:restapi:http_method_cant_make_sense [2023/06/25 09:48] (目前版本)
行 1: 行 1:
 {{tag>​rest}} {{tag>​rest}}
-====== Http Method無法表達出某些動作(施工中) ​======+====== Http Method無法表達出某些動作 ======
 ===== Introduction ===== ===== Introduction =====
 在設計RestAPI時,並非所有domain操作都符合CRUD;例如,model中資料的同步(sync)、有transaction的行如轉換(transfer)、搬移(move)、複製(copy)等;因此做些案例研究看是否能找尋到自己能滿意的做法。本篇文章是根據研究結果,分享針對操作(action)做resource modeling的心得。 在設計RestAPI時,並非所有domain操作都符合CRUD;例如,model中資料的同步(sync)、有transaction的行如轉換(transfer)、搬移(move)、複製(copy)等;因此做些案例研究看是否能找尋到自己能滿意的做法。本篇文章是根據研究結果,分享針對操作(action)做resource modeling的心得。
行 14: 行 14:
 PATCH /​Systems/​1/​BIOS/​Settings PATCH /​Systems/​1/​BIOS/​Settings
 </​code>​ </​code>​
-接著是更新firmware,以[[http://www.dell.com/support/manuals/tw/en/​twdhs1/​dell-active-system-mngr-v8.1/​asm%20rest%20api-v1/​manageddevicefirmware-put?​guid=guid-ad63b2c1-00c4-4ce3-ad7a-a498e15f6636&​lang=en-us|Dell ASM REST API]]為例,使用store resource的方式去操作:​+接著是更新firmware,以[[https://cpsdocs.dellemc.com/bundle/PFMGR_API_3/page/GUID-87FE88BA-FE97-4ECA-BF1E-46A23CC6414F.html|Dell ASM REST API]]為例,使用store resource的方式去操作:​
 <​code>​ <​code>​
 PUT /​ManagedDevice/​firmware PUT /​ManagedDevice/​firmware
行 40: 行 40:
 <​action>​ <​action>​
 </​code>​ </​code>​
-看起來好像很容易,但要做到這些需要經驗與想像力,否則model出來的resource可能會讓user覺得困惑。除此之外,還要考量是否有非同步需求;以更新firmware來說,雖然PUT不是不能回傳202,但比較少看到有人使用。由於以上原因,我們需一個比較簡單的方+看起來好像很容易,但要做到這些需要經驗與想像力,否則model出來的resource可能會讓user覺得困惑。除此之外,還要考量是否有非同步需求;以更新firmware來說,雖然PUT不是不能回傳202,但比較少看到有人使用。由於以上原因,接下來我要分享比較簡單且有不少案例的方
 ===== A simple way: store resource/​PATCH/​controller resource ===== ===== A simple way: store resource/​PATCH/​controller resource =====
 這個方法是根據[[http://​www.vinaysahni.com/​best-practices-for-a-pragmatic-restful-api|這篇best practice]]、 這個方法是根據[[http://​www.vinaysahni.com/​best-practices-for-a-pragmatic-restful-api|這篇best practice]]、
行 95: 行 95:
  
 ==== The procedural action ==== ==== The procedural action ====
-這個能列舉的範例很多,只要不是CRUD的操作,你可以選擇把它model唯一個controller resource:+這個能列舉的範例很多,只要不是CRUD的操作,你可以選擇把它modelcontroller resource:
 <​code>​ <​code>​
 Search Search
行 118: 行 118:
   - hypermedia: 我們有辦法表達出取得狀態、關機、開機等的link嗎?​   - hypermedia: 我們有辦法表達出取得狀態、關機、開機等的link嗎?​
   - synchronized:​ 在我們做開機與關機動作後,是可以立即反應的嗎? ​   - synchronized:​ 在我們做開機與關機動作後,是可以立即反應的嗎? ​
-  - implementation: ​這部分稍後再做說明。 +  - implementation: ​如果使用PATCH的話,我比較不是那麼喜愛,原因稍後再做說明。 
-或許把它做成store resource會比較好:​+考慮以上原因,或許把它做成store resource會比較好:​
 <​code>​ <​code>​
 GET /​hosts/​123/​power_status GET /​hosts/​123/​power_status
行 128: 行 128:
 這裡我並沒有使用DELETE,因為在語義上使用DELETE power_on不會比PUT power_off來得清楚。此外,在使用這種方式後,hypermedia可以很容易的使用URI去表達出不同的意義。剩下的問題就是synchronized。\\ 這裡我並沒有使用DELETE,因為在語義上使用DELETE power_on不會比PUT power_off來得清楚。此外,在使用這種方式後,hypermedia可以很容易的使用URI去表達出不同的意義。剩下的問題就是synchronized。\\
 \\ \\
-PUT回傳202的方式,我自己本身還沒看過範例,但HTTP規格書也沒說這樣是不對的。我覺得值得討論的部分是: ​Idempotent。假設PUT回傳202,這代表著server將產生一個asynchronized的task,每次PUT power_on所產生的task是否會相同呢?​ 假如不同,是不是代表違反了Idempotent? 假如相同,實作會不會蠻奇怪的呢?​\\+PUT回傳202的方式,目前有看過[[https://​techlibrary.hpe.com/​docs/​enterprise/​servers/​oneview5.0/​cicf-api/​en/​index.html#​rest/​enclosures|HPE OneView REST API]]存在這樣的設計;HTTP規格書也沒說這樣是不對的。我覺得值得討論的部分是: ​idempotent。假設PUT回傳202,這代表著server將產生一個asynchronized的task,每次PUT power_on所產生的task是否會相同呢?​ 假如不同,是不是代表違反了idempotent? 假如相同,實作會不會蠻奇怪的呢? ​這個我目前沒有答案。\\
 \\ \\
 另外一個選擇,就是把它當controller resource,使用POST去操作:​ 另外一個選擇,就是把它當controller resource,使用POST去操作:​
行 135: 行 135:
 POST /​hosts/​123/​power_off POST /​hosts/​123/​power_off
 </​code>​ </​code>​
-有個實際案例就是[[https://​pubs.vmware.com/​vcd-51/​index.jsp?​topic=/​com.vmware.vcloud.api.reference.doc_51/​doc/​operations/​POST-PowerOffVApp.html|vCloud]]的Power On/Off API。順便提一下,會使用這個範例是由於在Roy Fielding在2008年[[http://​roy.gbiv.com/​untangled/​2009/​it-is-okay-to-use-post|It is okay to use POST]]文章中,留言區有人提及;而Roy Fielding也是傾向於將狀態與操作model為不同狀態,這很好奇為什麼他會這樣想。+有個實際案例就是[[https://​pubs.vmware.com/​vcd-51/​index.jsp?​topic=/​com.vmware.vcloud.api.reference.doc_51/​doc/​operations/​POST-PowerOffVApp.html|vCloud]]的Power On/Off API。順便提一下,會使用這個範例是由於在Roy Fielding在2008年[[http://​roy.gbiv.com/​untangled/​2009/​it-is-okay-to-use-post|It is okay to use POST]]文章中,留言區有人提及此範例;而Roy Fielding也是傾向於將狀態與操作model為不同狀態,這使思考為什麼他會這樣想。
  
 ===== Some questions ===== ===== Some questions =====
-針對以上提到的方法,我思考著幾個問題:​+針對以上提到的方法,我思考著幾個問題:​
 ==== Why don't you use the query string or formdata to pass the action? ==== ==== Why don't you use the query string or formdata to pass the action? ====
 (這裡先撇開query string、formdata或request body等方式的差異) 對我而言,我目前認為有幾點需要考量:​ (這裡先撇開query string、formdata或request body等方式的差異) 對我而言,我目前認為有幾點需要考量:​
   - hypermedia: 假如要表達出query string等方式,會需要使用template的方式,實作上不會比單純透過URI Path的方式容易。   - hypermedia: 假如要表達出query string等方式,會需要使用template的方式,實作上不會比單純透過URI Path的方式容易。
   - http method convention: 如果在collection上使用POST,是否會讓新增與其它action讓使用者混淆。   - http method convention: 如果在collection上使用POST,是否會讓新增與其它action讓使用者混淆。
-  - implementation: ​ +  - implementation:​ 我們看看如果使用URI的Path來實做lock與unlock可能會長怎樣:​
-我們看看如果使用URI的Path來實做lock與unlock可能會長怎樣:​+
 <code java> <code java>
  @RequestMapping(value = "/​files/​{fid}/​lock",​ method = RequestMethod.PUT,​ produces = {"​application/​json"​})  @RequestMapping(value = "/​files/​{fid}/​lock",​ method = RequestMethod.PUT,​ produces = {"​application/​json"​})
行 170: 行 169:
  }  }
 </​code>​ </​code>​
-你喜歡哪個?​ 假如File的action只有lock與unlock,那真的是天下太平;但事實上,action還有move、copy、rename等。考慮一下測試、維護、擴充的話,哪一個會比較好?​ 以擴充與維護來說,RequestParam的方式讓所有的action都必須接受同一組參數甚至輸出,增加了修改的麻煩;URI Path則由各別的實做去決定。測試則是因為實做已分開,根據別操作的目的去測試即可。+你喜歡哪個?​ 假如File的action只有lock與unlock,那真的是天下太平;但事實上,action還有move、copy、rename等。考慮一下測試、維護、擴充的話,哪一個會比較好?​ 以擴充與維護來說,RequestParam的方式讓所有的action都必須接受同一組參數甚至輸出,增加了修改的麻煩;URI Path則由各別的實做去決定。測試則是因為實做已分開,根據別操作的目的去測試即可。
 ==== Threat the P/N action as a store resource or use PATCH? ==== ==== Threat the P/N action as a store resource or use PATCH? ====
 +在我們的專案中,P/​N action我傾向使用store resource大於PATCH。主要原因有以下:​ 
 +  * 合適的media type: PATCH所能使用的media type種類相當多,對於同時支援xml與json格式的我們,要選哪一條路還無定論。PATCH種類有機會再介紹。 
 +  * 不容易寫好維護的程式:​ 由於PATCH是部分更新,所以程式必須針對開放欄位做是否修改的判斷,也可能要為此打造輸入使用的物件。 
 +<code java> 
 +@PatchMapping(value = "/​files/​{id}"​) 
 +public ResponseEntity<​String>​ editFile(@RequestBody FileBean updateFile,  
 + @PathVariable("​id"​) String fileId){ 
 + File file = FileDao.getFile(fileId);​ 
 + if( file == null ) { 
 + return new ResponseEntity<​String>​("​File doesn'​t exist", ​ HttpStatus.NOT_FOUND);​ 
 +
 +  
 + if( updateFile.locked != null ) 
 + file.locked = Boolean.parseBoolean(updateFile.locked);​ 
 + if( updateFile.name != null ) 
 + file.name = updateFile.name;​ 
 + return new ResponseEntity<​String>​("​good", ​ HttpStatus.OK);​ 
 +
 +</​code>​ 
 +  * 符合多種情況的request參數:​ 面對不同的傳遞參數方式,目前我還沒找到一個共用的方法。 
 +這樣抉擇的發生,是建立在動作屬於Resource中的一個attribute時。
 ===== Summary ===== ===== Summary =====
-[[.:​http_method_cant_make_sense:​other_case_studies|其它的案例]]+面對一個非CRUD的domain操作,我們該如何model為resource呢?​  
 +  - 盡力把它變成service resource,可參考他人做法。 
 +  - 確認是否為一個procedure,是的話就把它當controller resource;可以把非同步的性質當前置條件。 
 +  - 確認是否為resource中的attribute,是的話可以使用PATCH。 
 +  - 如果不想用PATCH,可以考慮作為store resource。 
 +以上方法可以當參考,還是要以需求為重;另外補充我看過的[[.:​http_method_cant_make_sense:​other_case_studies|其它的案例]]
 ===== Reference ===== ===== Reference =====
   * [[https://​stackoverflow.com/​questions/​2173721/​why-does-including-an-action-verb-in-the-uri-in-a-rest-implementation-violate-th|Why does including an action verb in the URI in a REST implementation violate the protocol?]]   * [[https://​stackoverflow.com/​questions/​2173721/​why-does-including-an-action-verb-in-the-uri-in-a-rest-implementation-violate-th|Why does including an action verb in the URI in a REST implementation violate the protocol?]]