Tại sao lại nhắm đến một thiết kế RESTful?
Các nguyên tắc RESTful mang các tính năng giúp các trang web dễ dàng (cho một người dùng ngẫu nhiên của con người "lướt" chúng) vào thiết kế API dịch vụ web , do đó chúng dễ dàng cho lập trình viên sử dụng. REST không tốt vì nó là REST, nó tốt vì nó tốt. Và nó là tốt chủ yếu bởi vì nó là đơn giản .
Sự đơn giản của HTTP đơn giản (không có phong bì SOAP và các POST
dịch vụ quá tải URI đơn ), điều mà một số người có thể gọi là "thiếu tính năng" , thực sự là thế mạnh lớn nhất của nó . Ngay lập tức, HTTP yêu cầu bạn có địa chỉ và trạng thái không trạng thái : hai quyết định thiết kế cơ bản giúp HTTP có thể mở rộng lên đến các trang web lớn hiện nay (và các dịch vụ lớn).
Nhưng REST không phải là bulltet bạc: Đôi khi, kiểu RPC ("Cuộc gọi thủ tục từ xa" - chẳng hạn như SOAP) có thể phù hợp và đôi khi các nhu cầu khác được ưu tiên hơn các ưu điểm của Web. Điều này là tốt Những gì chúng ta không thực sự thích là sự phức tạp không cần thiết . Quá thường xuyên một lập trình viên hoặc một công ty mang đến các Dịch vụ theo kiểu RPC cho một công việc mà HTTP cũ đơn giản có thể xử lý tốt. Hiệu quả là HTTP được giảm xuống một giao thức truyền tải cho một tải trọng XML khổng lồ giải thích những gì "thực sự" đang diễn ra (không phải là URI hoặc phương thức HTTP đưa ra manh mối về nó). Dịch vụ kết quả quá phức tạp, không thể gỡ lỗi và sẽ không hoạt động trừ khi khách hàng của bạn có thiết lập chính xác như nhà phát triển dự định.
Tương tự như vậy, mã Java / C # có thể không hướng đối tượng, chỉ sử dụng HTTP không tạo ra thiết kế RESTful. Người ta có thể bị cuốn vào suy nghĩ vội vàng về các dịch vụ của họ về các hành động và phương thức từ xa nên được gọi. Không có gì ngạc nhiên khi điều này chủ yếu sẽ kết thúc trong một dịch vụ RPC-Style (hoặc REST-RPC-hybrid). Bước đầu tiên là suy nghĩ khác biệt. Một thiết kế RESTful có thể đạt được bằng nhiều cách, một cách là nghĩ về ứng dụng của bạn về mặt tài nguyên, chứ không phải hành động:
Thay vì suy nghĩ về các hành động, nó có thể thực hiện ("thực hiện tìm kiếm địa điểm trên bản đồ") ...
... Cố gắng suy nghĩ về kết quả của những hành động đó ("danh sách các địa điểm trên bản đồ phù hợp với tiêu chí tìm kiếm").
Tôi sẽ lấy ví dụ dưới đây. (Khía cạnh quan trọng khác của REST là việc sử dụng HATEOAS - Tôi không đánh nó ở đây, nhưng tôi nói nhanh về nó ở một bài khác .)
Các vấn đề của thiết kế đầu tiên
Chúng ta hãy xem một thiết kế được đề xuất:
ACTION http://api.animals.com/v1/dogs/1/
Trước hết, chúng ta không nên xem xét việc tạo một động từ HTTP mới ( ACTION
). Nói chung, điều này là không mong muốn vì nhiều lý do:
- (1) Chỉ được cung cấp URI dịch vụ, làm thế nào một lập trình viên "ngẫu nhiên" sẽ biết
ACTION
động từ tồn tại?
- (2) nếu lập trình viên biết nó tồn tại, làm thế nào anh ta biết ngữ nghĩa của nó? Động từ đó có nghĩa là gì?
- (3) những tính chất nào (an toàn, không cần thiết) người ta nên mong đợi động từ đó có?
- (4) nếu lập trình viên có một máy khách rất đơn giản chỉ xử lý các động từ HTTP tiêu chuẩn thì sao?
- (5) ...
Bây giờ hãy xem xét việc sử dụngPOST
(Tôi sẽ thảo luận về lý do tại sao bên dưới, hãy hiểu ý tôi ngay bây giờ):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
Điều này có thể ổn ... nhưng chỉ khi :
{"action":"bark"}
là một tài liệu; và
/v1/dogs/1/
là một URI "bộ xử lý tài liệu" (giống như nhà máy). "Bộ xử lý tài liệu" là một URI mà bạn chỉ cần "ném mọi thứ" và "quên" về chúng - bộ xử lý có thể chuyển hướng bạn đến một tài nguyên mới được tạo sau khi "ném". Ví dụ: URI để đăng tin nhắn tại dịch vụ môi giới tin nhắn, sau khi đăng sẽ chuyển hướng bạn đến URI hiển thị trạng thái xử lý tin nhắn.
Tôi không biết nhiều về hệ thống của bạn, nhưng tôi đã cá rằng cả hai đều không đúng:
{"action":"bark"}
không phải là một tài liệu , nó thực sự là phương pháp mà bạn đang cố gắng lén lút vào dịch vụ; và
- các
/v1/dogs/1/
URI đại diện cho một "con chó" tài nguyên (có thể là con chó với id==1
) và không phải là một bộ vi xử lý tài liệu.
Vì vậy, tất cả những gì chúng ta biết bây giờ là thiết kế ở trên không quá RESTful, nhưng đó chính xác là gì? Điều gì là xấu về nó? Về cơ bản, nó là xấu vì đó là URI phức tạp với ý nghĩa phức tạp. Bạn không thể suy ra bất cứ điều gì từ nó. Làm thế nào một lập trình viên biết một con chó có một bark
hành động có thể được bí mật truyền POST
vào nó?
Thiết kế các lệnh gọi API của câu hỏi của bạn
Vì vậy, hãy cắt giảm để theo đuổi và cố gắng thiết kế những tiếng sủa đó một cách khéo léo bằng cách suy nghĩ về các nguồn lực . Cho phép tôi trích dẫn cuốn sách Dịch vụ web đầy đủ :
Một POST
yêu cầu là một nỗ lực để tạo ra một nguồn lực mới từ một hiện có. Tài nguyên hiện có có thể là cha mẹ của cái mới theo nghĩa cấu trúc dữ liệu, cách gốc của cây là cha mẹ của tất cả các nút lá của nó. Hoặc tài nguyên hiện có có thể là tài nguyên "nhà máy" đặc biệt
với mục đích duy nhất là tạo ra các tài nguyên khác. Đại diện được gửi cùng với một POST
yêu cầu mô tả trạng thái ban đầu của tài nguyên mới. Như với PUT, một POST
yêu cầu hoàn toàn không cần bao gồm một đại diện.
Theo mô tả ở trên, chúng ta có thể thấy rằng bark
có thể được mô hình hóa như là một nguồn con của mộtdog
(vì một con bark
được chứa trong một con chó, nghĩa là, một con chó sủa bị "sủa" bởi một con chó).
Từ lý do đó, chúng tôi đã có:
- Phương pháp là
POST
- Tài nguyên là
/barks
, nguồn cung cấp con chó : /v1/dogs/1/barks
, đại diện cho một bark
"nhà máy". URI đó là duy nhất cho mỗi con chó (vì nó nằm dưới /v1/dogs/{id}
).
Bây giờ mỗi trường hợp danh sách của bạn có một hành vi cụ thể.
1. vỏ cây chỉ gửi e-mail đến dog.email
và ghi lại không có gì.
Thứ nhất, việc sủa (gửi e-mail) là một nhiệm vụ đồng bộ hay không đồng bộ? Thứ hai, bark
yêu cầu có yêu cầu bất kỳ tài liệu nào (e-mail, có thể) hoặc nó trống?
1.1 vỏ cây gửi e-mail đến dog.email
và ghi lại không có gì (như một nhiệm vụ đồng bộ)
Trường hợp này là đơn giản. Một cuộc gọi đến barks
tài nguyên nhà máy mang lại một vỏ cây (một e-mail được gửi) ngay lập tức và phản hồi (nếu OK hoặc không) được đưa ra ngay lập tức:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
Vì nó ghi lại (thay đổi) không có gì, 200 OK
là đủ. Nó cho thấy rằng mọi thứ đã đi như mong đợi.
1.2 vỏ cây gửi e-mail đến dog.email
và ghi lại không có gì (như một nhiệm vụ không đồng bộ)
Trong trường hợp này, khách hàng phải có cách theo dõi bark
nhiệm vụ. Các bark
nhiệm vụ sau đó phải là một tài nguyên với riêng URI nó .:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Bằng cách này, mỗi bark
là có thể truy nguyên. Sau đó, khách hàng có thể đưa ra một GET
đến bark
URI để biết đó là tình trạng hiện thời. Thậm chí có thể sử dụng một DELETE
để hủy bỏ nó.
2. vỏ cây gửi e-mail đến dog.email
và sau đó tăng thêm dog.barkCount
1
Điều này có thể phức tạp hơn, nếu bạn muốn cho khách hàng biết dog
tài nguyên được thay đổi:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
Trong trường hợp này, location
mục đích của người đứng đầu là để cho khách hàng biết rằng anh ta nên xem qua dog
. Từ RFC HTTP về303
:
Phương thức này tồn tại chủ yếu để cho phép đầu ra của
POST
tập lệnh được kích hoạt để chuyển hướng tác nhân người dùng đến tài nguyên đã chọn.
Nếu tác vụ không đồng bộ, cần có một bark
nguồn cung cấp phụ giống như 1.2
tình huống và 303
phải được trả lại vào lúc GET .../barks/Y
nhiệm vụ hoàn thành.
3. vỏ cây tạo ra một bark
bản ghi "" mới với bark.timestamp
ghi âm khi vỏ cây xảy ra. Nó cũng tăng thêm dog.barkCount
1.
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Ở đây, bark
một được tạo do yêu cầu, vì vậy trạng thái 201 Created
được áp dụng.
Nếu việc tạo không đồng bộ, thay vào đó , 202 Accepted
cần phải có ( như RFC HTTP nói ).
Dấu thời gian được lưu là một phần của bark
tài nguyên và có thể được truy xuất bằng a GET
. Con chó cập nhật cũng có thể được "ghi lại" trong đó GET dogs/X/barks/Y
.
4. vỏ cây chạy một lệnh hệ thống để kéo phiên bản mới nhất của mã chó xuống từ Github. Sau đó, nó sẽ gửi một tin nhắn văn bản để dog.owner
nói với họ rằng mã chó mới đang được sản xuất.
Từ ngữ của cái này rất phức tạp, nhưng nó khá nhiều là một nhiệm vụ không đồng bộ đơn giản:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Sau đó, khách hàng sẽ phát hành GET
s để /v1/dogs/1/barks/a65h44
biết trạng thái hiện tại (nếu mã được kéo, e-mail đã được gửi cho chủ sở hữu và như vậy). Bất cứ khi nào con chó thay đổi, a 303
là đáng khen ngợi.
Gói lại
Trích dẫn Roy Fielding :
Điều duy nhất REST yêu cầu các phương thức là chúng được xác định thống nhất cho tất cả các tài nguyên (nghĩa là để các trung gian không phải biết loại tài nguyên để hiểu ý nghĩa của yêu cầu).
Trong các ví dụ trên, POST
được thiết kế thống nhất. Nó sẽ làm cho con chó " bark
". Điều đó không an toàn (có nghĩa là vỏ cây có ảnh hưởng đến tài nguyên), cũng không phải là idempotent (mỗi yêu cầu mang lại một cái mới bark
), rất phù hợp với POST
động từ.
Một lập trình viên sẽ biết: a POST
để barks
mang lại a bark
. Mã trạng thái phản hồi (cũng với phần thân thực thể và tiêu đề khi cần thiết) thực hiện công việc giải thích những gì đã thay đổi và cách khách hàng có thể và nên tiến hành.
Lưu ý: Các nguồn chính được sử dụng là: sách " Dịch vụ web đầy đủ ", blog của HTTP RFC và Roy Fielding .
Biên tập:
Câu hỏi và do đó, câu trả lời đã thay đổi khá nhiều kể từ khi chúng được tạo lần đầu tiên. Câu hỏi ban đầu hỏi về thiết kế của một URI như:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Dưới đây là lời giải thích tại sao nó không phải là một lựa chọn tốt:
Làm thế nào khách hàng nói với máy chủ PHẢI LÀM GÌ với dữ liệu là thông tin phương pháp .
- Các dịch vụ web RESTful truyền tải thông tin phương thức trong phương thức HTTP.
- Các dịch vụ RPC-Style và SOAP điển hình giữ cho chúng trong tiêu đề thực thể và HTTP.
Phần nào của dữ liệu [máy khách muốn máy chủ] hoạt động là thông tin phạm vi .
- Các dịch vụ RESTful sử dụng URI. Các dịch vụ SOAP / RPC-Style một lần nữa sử dụng các tiêu đề thực thể và HTTP.
Ví dụ: lấy URI của Google http://www.google.com/search?q=DOG
. Ở đó, thông tin phương pháp là GET
và thông tin phạm vi là /search?q=DOG
.
Mẩu chuyện dài:
- Trong các kiến trúc RESTful , thông tin phương thức đi vào phương thức HTTP.
- Trong Kiến trúc hướng tài nguyên , thông tin phạm vi đi vào URI.
Và quy tắc của ngón tay cái:
Nếu phương thức HTTP không khớp với thông tin phương thức, thì dịch vụ không phải là RESTful. Nếu thông tin phạm vi không có trong URI, thì dịch vụ không hướng đến tài nguyên.
Bạn có thể đặt "hành động" "vỏ cây" trong URL (hoặc trong phần thân thực thể) và sử dụng . Không có vấn đề ở đó, nó hoạt động và có thể là cách đơn giản nhất để làm điều đó, nhưng đây không phải là RESTful .POST
Để giữ cho dịch vụ của bạn thực sự RESTful, bạn có thể phải lùi lại một bước và suy nghĩ về những gì bạn thực sự muốn làm ở đây (nó sẽ có tác dụng gì đối với tài nguyên).
Tôi không thể nói về nhu cầu kinh doanh cụ thể của bạn, nhưng hãy để tôi cho bạn một ví dụ: Hãy xem xét một dịch vụ đặt hàng RESTful trong đó các đơn đặt hàng tại URI như thế nào example.com/order/123
.
Bây giờ nói rằng chúng tôi muốn hủy một đơn đặt hàng, làm thế nào chúng ta sẽ làm điều đó? Người ta có thể bị cám dỗ để nghĩ rằng đó là một "hành động" "hủy bỏ " và thiết kế nó như là POST example.com/order/123?do=cancel
.
Đó không phải là RESTful, như chúng ta đã nói ở trên. Thay vào đó, chúng ta có thể PUT
là một đại diện mới của order
với một canceled
yếu tố gửi đến true
:
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
Và đó là nó. Nếu đơn đặt hàng không thể bị hủy, một mã trạng thái cụ thể có thể được trả lại. (Một thiết kế nguồn phụ, như POST /order/123/canceled
với cơ thể thực thể true
có thể, để đơn giản, cũng có sẵn.)
Trong kịch bản cụ thể của bạn, bạn có thể thử một cái gì đó tương tự. Bằng cách đó, trong khi một con chó đang sủa, ví dụ, GET
tại /v1/dogs/1/
có thể bao gồm thông tin đó (ví dụ <barking>true</barking>
) . Hoặc ... nếu điều đó quá phức tạp, hãy nới lỏng yêu cầu RESTful của bạn và kiên trì thực hiện POST
.
Cập nhật:
Tôi không muốn làm cho câu trả lời quá lớn, nhưng phải mất một thời gian để hiểu được việc phơi bày một thuật toán (một hành động ) dưới dạng một tập hợp các tài nguyên. Thay vì suy nghĩ về các hành động ( "tìm kiếm địa điểm trên bản đồ" ), người ta cần suy nghĩ về kết quả của hành động đó ( "danh sách các địa điểm trên bản đồ phù hợp với tiêu chí tìm kiếm" ).
Bạn có thể thấy mình quay lại bước này nếu bạn thấy rằng thiết kế của bạn không phù hợp với giao diện thống nhất của HTTP.
Các biến truy vấn là thông tin phạm vi , nhưng không biểu thị các tài nguyên mới ( /post?lang=en
rõ ràng là cùng một tài nguyên với /post?lang=jp
, chỉ là một đại diện khác). Thay vào đó, chúng được sử dụng để truyền đạt trạng thái máy khách (như ?page=10
, để trạng thái đó không được giữ trong máy chủ; ?lang=en
cũng là một ví dụ ở đây) hoặc các tham số đầu vào cho tài nguyên thuật toán ( /search?q=dogs
, /dogs?code=1
). Một lần nữa, không phải là tài nguyên riêng biệt.
Thuộc tính (phương thức) của động từ HTTP:
Một điểm rõ ràng khác cho thấy ?action=something
trong URI không phải là RESTful, là các thuộc tính của động từ HTTP:
GET
và HEAD
được an toàn (và bình thường);
PUT
và DELETE
chỉ là idempotent;
POST
không phải là.
An toàn : A GET
hoặc HEAD
yêu cầu là yêu cầu đọc một số dữ liệu, không phải yêu cầu thay đổi bất kỳ trạng thái máy chủ nào. Khách hàng có thể thực hiện GET
hoặc HEAD
yêu cầu 10 lần và nó giống như thực hiện một lần hoặc hoàn toàn không bao giờ thực hiện .
Idempotence : Một phép toán idempotent trong một hoạt động có cùng tác dụng cho dù bạn áp dụng nó một lần hay nhiều lần (trong toán học, nhân với số 0 là idempotent). Nếu bạn DELETE
tài nguyên một lần, xóa lại sẽ có tác dụng tương tự (tài nguyên GONE
đã có).
POST
không an toàn cũng không bình thường. Thực hiện hai POST
yêu cầu giống hệt nhau đối với tài nguyên 'nhà máy' có thể sẽ dẫn đến hai tài nguyên cấp dưới chứa cùng một thông tin. Với tình trạng quá tải (phương thức trong URI hoặc thực thể) POST
, tất cả các cược đã tắt.
Cả hai thuộc tính này đều quan trọng đối với sự thành công của giao thức HTTP (trên các mạng không đáng tin cậy!): Bạn đã cập nhật ( GET
) trang bao nhiêu lần mà không cần đợi cho đến khi nó được tải đầy đủ?
Tạo một hành động và đặt nó vào URL rõ ràng phá vỡ hợp đồng của các phương thức HTTP. Một lần nữa, công nghệ cho phép bạn, bạn có thể làm điều đó, nhưng đó không phải là thiết kế RESTful.