Phần lớn các API được viết bằng HTTP, mà chúng ta đều đã biết về cách dùng HTTP methods theo chuẩn RESTful khi thiết kế Web API. Tuy nhiên, nhận thấy ngay là HTTP methods đã được thiết kế như từ đầu rồi, RESTful API chỉ là nguyên tắc dựa theo chuẩn này thôi, hay nói cách khác là phần ngọn. Vì vậy thì để thiết kế HTTP API cho ngon lành thì có vẻ chúng ta cần đọc lại 1 chút về gốc gác của HTTP methods.

Phần lớn các API được viết bằng HTTP, mà chúng ta đều đã biết về cách dùng HTTP methods theo chuẩn RESTful khi thiết kế Web API (Nếu bạn chưa biết, đọc thêm ở đây). Tuy nhiên, nhận thấy ngay là HTTP methods đã được thiết kế như từ đầu rồi, RESTful API chỉ là nguyên tắc dựa theo chuẩn này thôi. Vì vậy thì để thiết kế HTTP API cho ngon lành thì có vẻ chúng ta cần đọc lại 1 chút về HTTP methods.

Hiện tại thì HTTP đã ra tới bản thứ 3, tuy nhiên HTTP methods không bị cập nhật gì, vậy nên mình sẽ bắt đầu với RFC-7231

Common Method Properties

OK, chưa tính tới chi tiết các methods, hãy bắt đầu từ các thuộc tính chung của một method Common Method Properties . Theo đoạn này thì một HTTP methods có thể có các thuộc tính chung sau:

Safe Methods

Là các methods kiểu read-only, tức là nó chỉ lấy thông tin từ server về mà không thay đổi trạng thái của tài nguyên. Các method thay đổi trạng thái tài nguyên còn lại dĩ nhiên là Unsafe.

Idempotent Methods

Là các methods mà khi gửi một request nhiều lần thì tác động lên tài nguyên server là giống như gửi một lần. Nói cách khác thì request dạng này là request có thể lặp lại (repeat).

Hiển nhiên các Safe Methods cũng là Idempotent Methods.

Cacheable Methods

Như tên, method nào thì cache được. Tuy nhiên, phải hiểu đúng ở đây là có thể cache, chứ không phải lúc nào cũng cache.

Tóm lại, chúng ta có bảng sau

https://i.postimg.cc/gJ0VRfSd/Screen-Shot-2020-03-15-at-5-38-28-PM.png

PUT là UPSERT - PATCH là UPDATE

Có khá nhiều tài liệu ghi PUT là update, nhưng theo design của HTTP, PUT là request theo kiểu tôi muốn đối tượng này tồn tại trên server.

  • Nếu đối tượng đã tồn tại, replace nó.
  • Nếu đối tượng chưa tồn tại, tạo mới nó.

Điều này có nghĩa, sau khi gọi PUT, chắc chắn sẽ tồn tại đối tượng vừa PUT trên . Và với tính chất replace or create, PUT nên được thiết kế với body là một đối tượng hoàn chỉnh. Ví dụ:

Nếu chúng ta có đối tượng user như sau

1
2
3
4
5
6
{
"id": "string",
"first_name": "string",
"last_name": "string",
"website": "string"
}

Khi đó PUT 1 user lên server sẽ cần đầy đủ các field, dù trước đó đã tồn tại user này hay chưa

1
2
3
4
5
6
7
8
PUT /users/tienhm

{
"id": "tienhm",
"first_name": "Tien",
"last_name": "Hoang",
"website": "tienhm.me"
}

Đừng trả về NOT FOUND cho DELETE

Cũng giống như PUT, DELETE là Idempotent Method. Có nghĩa là nếu chúng ta gọi DELETE 2 lần liên tiếp, thì kết quả tương tự như gọi 1 lần. Với tính chất này, có thể hiểu DELETE là hàm để đảm bảo đối tượng không còn khả dụng trên server.

DELETE nên được thiết kế kiểu này:

1
2
3
4
5
DELETE /users/tienhm
200 OK

DELETE /users/tienhm
200 OK

Hơn là kiểu này:

1
2
3
4
5
DELETE /users/tienhm
200 OK

DELETE /users/tienhm
404 Not Found

Dù POST là cacheable, nhưng …

Bắt đầu với quote mà mình siêu thích

There are only two hard things in Computer Science: cache invalidation and naming things.

Rồi, thực sự việc cache cho POST là cái gì đó khá dị. Và nó thực sự dị nếu đọc chỉ qua. Cụ thể, RFC-7234 có viết về Cache Invalidation như sau:

1
2
3
4
5
6
7
8
9
10
11
4.4.  Invalidation

Because unsafe request methods (Section 4.2.1 of [RFC7231]) such as
PUT, POST or DELETE have the potential for changing state on the
origin server, intervening caches can use them to keep their contents
up to date.

A cache MUST invalidate the effective Request URI (Section 5.5 of
[RFC7230]) as well as the URI(s) in the Location and Content-Location
response header fields (if present) when a non-error status code is
received in response to an unsafe request method.

Đó, Unsafe method đã không cache rồi, POST nó còn không phải Idempotent, thế nên cache của POST khá khó hiểu. Sau 1 lúc đọc lòng vòng thì mình cũng hiểu ra vấn đề như sau:

Cơ bản thì việc cache cho POST là cực kỳ hạn chế và ít xảy ra. POST chỉ được cache khi có thông tin về cache (kiểu max-age hoặc Expires). Tuy nhiên, cache của POST dị là vì nó chỉ cache response của POST chứ không phải cache request. Cụ thể:

  • Cache response của POST để dùng cho cái GET tiếp theo: bằng cách set Content-Location hoặc Location header.
  • Request POST tiếp theo không có cache gì cả.

Tuy nhiên, mặc dù RFC cho phép việc cache này, nhưng cách này là cache response của request POST cho request GET, nên có một số CDN hoặc browser không cho cache kiểu này (đoạn này là đọc trên Stack Overflow - chưa kiểm chứng - thấy Firefox có support theo RFC rồi)

Tóm lại

  • Nhiều khi bạn viết thế nào cũng chạy được, kể cả GET /delete_user?id=tienhm, tuy nhiên làm theo chuẩn thì dễ hơn cho đồng đội và cả đối tác nữa.
  • Việc hiểu bản chất sẽ làm mình dễ nhớ hơn. Giờ ít nhất cũng không cần băn khoăn giữa PUT và PATCH chẳng hạn.
  • Các method còn lại như HEAD, OPTIONS, TRACE cũng có nhiều thứ khá hay, nên đọc thêm.
  • Lý thuyết là thế, thực tiễn là thực tiễn .Nếu đối tác của bạn là 1 công ty to đùng, và làm sai chuẩn, và bạn đang cần tích hợp vào hệ thống của họ, thì thôi, đừng tranh cãi làm gì nhé =))