2015年3月20日 星期五

遵循REST原則設計API,卻覺得不滿足,怎麼辦?

突然想起了蘇慧倫的【 滿足】,"你滿足了我不滿足~用什麼來彌補~"

設計web API時, 遵循REST架構的好處很多,這邊就不多提了,這邊我想紀錄一下自己在設計過程中遇到的一些問題與思維。

借用Ruby on Rails實戰聖經裡的例子:

HelperGETPOSTPATCH/PUTDELETE
event_path(@event)/events/1
show action
/events/1
update action
/events/1
destroy action
events_path/events
index action
/events
create action

新增一筆事件時,我們用:
POST /events
要取得多筆事件時,我們用:
GET /events
問題來了,如果我們想要一次新增多筆事件時,該怎麼辦?



首先,必須先思考是否一定要這麼做,是否發多個POST request就能夠滿足需求,如果一定要透過一個request來新增多筆事件,
我們可以怎麼做呢?假設"event"是我們的一種resource,平時新增一筆事件時,body帶的資料如下(以JSON為例):
{
    "date": "2015-7-18",
    "subject": "summer event",
    "location": "Taipei"
}
Server會回傳:
{
    "id": 1
}

想像我們有另一種resource,叫做"event-set",怎我們可以定義一個API為:
POST /event-set
可以帶這樣的body:
[
    {
        "date": "2015-7-18",
        "subject": "summer event",
        "location": "Taipei"
    },
    {
        "date": "2015-9-26",
        "subject": "fall event",
        "location": "Kaohsiung"
    },
    {
        "date": "2015-12-22",
        "subject": "winter event",
        "location": "Taichung"
    }
]
Server則需根據原本順序回傳每筆event的id:
[
    {
        "id": "1"
    },
    {
        "id": "2"
    },
    {
        "id": "3"
    }
]
則client就能夠針對回傳的每個id,針對每個event操作。寫到這可能有人已經跳腳了,沒錯,我定義了一個叫"event-set"的resource,但卻沒有提供對"event-set"的identification,這樣,還算是RESTful嗎?
這樣做有點破壞了統一介面的特性,破壞了RESTful簡單的特性,也需要考慮client一次發送過多的新增數量(例如一個request要求建立一百萬筆事件)造成的延遲等問題。沒有一種能夠治百病的架構,想清楚自己的目的是什麼,為何如此設計。REST很適合作為web應用的架構風格,而自己一定更清楚自己的需求。

一次要修改多筆event可以這麼做(同樣的,必須想清楚是否真的需要這樣的API):
{
    "1": {
        "data": "2015-7-19"
    },
    "2": {
        "subject": "autumn event"
    }
}



另一個問題,假設每個event下有多個訂單(order),則我們可以設計以下的API來操作訂單:
POST /events/1/orders
GET /events/1/orders
GET /events/1/orders/1
PATCH /events/1/orders/2
DELETE /events/1/orders/3

但這樣的設計,如果套用在實際應用上時,發現每筆訂單其實都要分法給不同單位各自處理,而每個單位並不知道也不需要知道該訂單適用在哪個event上,若我們經常需要獨立對每筆訂單做操作,則我們可以改成這樣的API設計:
POST /orders
GET /orders/a3e59a2c16ff44c69841db98dcd1a618
GET /orders
PATCH /orders/b2310ed23e534568876d1a3dff626690
DELETE /orders/67f1a8af50b8464ba7f8-741b20aba4a
不同單位的訂單可以做不同的權限控制,以避免被其他單位存取。同時,改用hash number作為id,可以避免一些存取失誤,例如order 3原來是給event 2的,但不小心拿了order 3給event 3使用。
而我們仍需要知道每筆order是屬於哪個event,因此可以把order id加在每個event的內容中:
GET /events/1
{
    "date": "2015-7-18",
    "subject": "summer event",
    "location": "Taipei",
    "orders": [
        "a3e59a2c16ff44c69841db98dcd1a618",
        "b2310ed23e534568876d1a3dff626690",
        ...
    ]
}
但這麼做的話,會導致order資訊永遠都會伴隨著event被提供出來,若想避免此問題,可以再加上query string來決定是否回傳event資訊時同時回傳order資訊

沒有留言 :

張貼留言