RESTFul: hành động thay đổi trạng thái


59

Tôi đang lên kế hoạch xây dựng API RESTfull nhưng có một số câu hỏi về kiến ​​trúc đang tạo ra một số vấn đề trong đầu. Thêm logic kinh doanh phụ trợ cho khách hàng là tùy chọn mà tôi muốn tránh vì việc cập nhật nhiều nền tảng máy khách khó có thể duy trì trong thời gian thực khi logic kinh doanh có thể thay đổi nhanh chóng.

Hãy nói rằng chúng tôi có bài viết dưới dạng tài nguyên (api / bài viết), chúng tôi nên thực hiện các hành động như xuất bản, hủy xuất bản, kích hoạt hoặc hủy kích hoạt như thế nào nhưng để cố gắng giữ nó đơn giản nhất có thể?

1) Chúng ta có nên sử dụng api / article / {id} / {action} vì rất nhiều logic phụ trợ có thể xảy ra ở đó như đẩy đến các vị trí từ xa hoặc thay đổi nhiều thuộc tính. Có lẽ điều khó nhất ở đây là chúng tôi cần gửi lại tất cả dữ liệu bài viết cho API để cập nhật và công việc nhiều người dùng không thể thực hiện được. Ví dụ, biên tập viên có thể gửi dữ liệu cũ hơn 5 giây và ghi đè lên bản sửa lỗi mà một số nhà báo khác mới làm cách đây 2 giây và tôi không thể giải thích cho khách hàng điều này vì những bài báo đó thực sự không liên quan đến việc cập nhật nội dung.

2) Tạo tài nguyên mới cũng có thể là một tùy chọn, api / article- {action} / id, nhưng sau đó tài nguyên được trả lại sẽ không phải là bài viết- {hành động} nhưng bài viết mà tôi không chắc liệu điều này có đúng không. Ngoài ra, trong lớp bài viết mã phía máy chủ đang xử lý Actuall hoạt động trên cả hai tài nguyên và tôi không chắc liệu điều này có đi ngược lại với suy nghĩ của RESTfull không

Mọi đề nghị đều được hoan nghênh ..


Hoàn toàn hợp pháp khi có 'hành động' là một phần của URI RESTful - nếu chúng nêu một hành động / thuật toán được thực hiện. Có chuyện gì với bạn api/article?action=publishvậy? Các tham số truy vấn được dành cho các trường hợp như vậy trong đó trạng thái của tài nguyên phụ thuộc vào 'thuật toán' (hoặc hành động) mà bạn đề cập. Ví dụ: api/articles?sort=aschợp lệ
Tiến sĩ

1
Tôi khuyên bạn nên kiểm tra bài viết này , có thể truyền cảm hứng cho bạn với một giải pháp RESTful hơn nữa .
Stewol

Một trong những vấn đề tôi gặp phải với api / article? Action = Publish là trong ứng dụng RESTfull nên gửi TẤT CẢ dữ liệu bài viết để xuất bản trong khi tôi chỉ muốn làm điều này: api / article / 4545 / xuất bản / không có gì thêm
Miro Svrtan

2
Tài nguyên tuyệt vời về vấn đề này và hơn thế nữa: Thiết kế API REST - Mô hình hóa tài nguyên
Izhaki

@PhD: mặc dù nó hoàn toàn hợp pháp trong giao thức, nhưng URI thường phải là danh từ hơn là động từ. Có một động từ trong URI thường là dấu hiệu của thiết kế REST xấu.
Nói dối Ryan

Câu trả lời:


49

Tôi thấy các thực hành được mô tả ở đây là hữu ích:

Còn những hành động không phù hợp với thế giới hoạt động của CRUD thì sao?

Đây là nơi mọi thứ có thể trở nên mờ nhạt. Có một số cách tiếp cận:

  1. Tái cấu trúc hành động để xuất hiện như một lĩnh vực tài nguyên. Điều này hoạt động nếu hành động không có tham số. Ví dụ, một hành động kích hoạt có thể được ánh xạ tới activatedtrường boolean và được cập nhật thông qua một PATCH vào tài nguyên.
  2. Đối xử với nó như một tài nguyên phụ với các nguyên tắc RESTful. Ví dụ, API GitHub của phép bạn sao một ý chính với PUT /gists/:id/starBỏ dấu sao với DELETE /gists/:id/star.
  3. Đôi khi bạn thực sự không có cách nào để ánh xạ hành động đến một cấu trúc RESTful hợp lý. Ví dụ: tìm kiếm đa tài nguyên không thực sự có ý nghĩa để được áp dụng cho điểm cuối của một tài nguyên cụ thể. Trong trường hợp này, /searchsẽ có ý nghĩa nhất mặc dù nó không phải là tài nguyên. Điều này là ổn - chỉ cần làm những gì đúng theo quan điểm của người tiêu dùng API và đảm bảo rằng nó được ghi lại rõ ràng để tránh nhầm lẫn.

Tôi bỏ phiếu cho cách tiếp cận 2. Mặc dù có vẻ như tài nguyên nhân tạo vụng về cho người gọi API, nhưng thực tế họ không biết những gì đang xảy ra trên máy chủ. Nếu tôi gọi POST /article/123/deactivationsđể tạo yêu cầu hủy kích hoạt mới cho bài viết 123, máy chủ có thể không chỉ hủy kích hoạt tài nguyên được yêu cầu mà còn lưu trữ yêu cầu hủy kích hoạt của tôi để tôi có thể truy xuất trạng thái của nó sau này.
JustAMartin

2
tại sao PUT /gists/:id/star không POST /gists/:id/star?
Filip Bartuzi

9
@FilipBartuzi Bởi vì PUT là idempotent - nghĩa là, cho dù bạn có thực hiện một hành động với cùng một tham số bao nhiêu lần đi chăng nữa, kết quả luôn giống nhau (ví dụ: nếu bạn bật đèn từ bật sang bật, nó sẽ thay đổi. nó lại tiếp tục, không có gì thay đổi - nó đã được bật lên). POST không phải là idempotent - nghĩa là mỗi lần bạn thực hiện một hành động, thậm chí với cùng một tham số, hành động đó có một kết quả khác nhau (ví dụ: nếu bạn gửi thư cho ai đó, người đó sẽ nhận được một lá thư. Nếu bạn gửi một lá thư giống hệt đến cùng một người, bây giờ họ có 2 chữ cái).
Raphael

9

Các hoạt động dẫn đến thay đổi trạng thái và hành vi chính ở phía máy chủ như hành động "xuất bản" mà bạn mô tả rất khó để mô hình hóa rõ ràng trong REST. Một giải pháp tôi thường thấy là điều khiển hành vi phức tạp như vậy thông qua dữ liệu.

Xem xét việc đặt hàng thông qua API REST được hiển thị bởi một thương gia trực tuyến. Đặt hàng là một hoạt động phức tạp. Một số sản phẩm sẽ được đóng gói và vận chuyển, tài khoản của bạn sẽ bị tính phí và bạn sẽ nhận được biên nhận. Bạn có thể hủy đơn đặt hàng của mình trong một khoảng thời gian giới hạn và tất nhiên có bảo đảm hoàn lại tiền đầy đủ cho phép bạn gửi lại sản phẩm để được hoàn lại tiền.

Thay vì một hoạt động mua hàng phức tạp, một API như vậy có thể cho phép bạn tạo một tài nguyên mới, một đơn đặt hàng. Lúc đầu, bạn có thể thực hiện bất kỳ sửa đổi nào bạn muốn: thêm hoặc xóa sản phẩm, thay đổi địa chỉ giao hàng, chọn tùy chọn thanh toán khác hoặc hủy đơn hàng hoàn toàn. Bạn có thể làm tất cả những điều này bởi vì bạn chưa mua bất cứ thứ gì, bạn chỉ đang thao túng một số dữ liệu trên máy chủ.

Khi đơn đặt hàng của bạn hoàn tất và thời gian ân hạn của bạn trôi qua, máy chủ sẽ khóa đơn đặt hàng của bạn để ngăn chặn mọi thay đổi khác. Chỉ tại thời điểm này, chuỗi hoạt động phức tạp bắt đầu, nhưng bạn không thể kiểm soát nó trực tiếp, chỉ gián tiếp thông qua dữ liệu bạn đã đặt trước đó vào đơn đặt hàng.

Dựa trên mô tả của bạn, "xuất bản" có thể được thực hiện theo cách này. Thay vì phơi bày một hoạt động, bạn đặt một bản sao của bản nháp mà bạn đã xem xét và muốn xuất bản dưới dạng tài nguyên mới trong / xuất bản. Điều này đảm bảo mọi cập nhật tiếp theo cho dự thảo sẽ không được công bố ngay cả khi chính hoạt động xuất bản hoàn thành sau nhiều giờ.


Ý tưởng thay đổi toàn bộ tài nguyên từ bài viết chưa được công bố thành bản nháp sẽ phù hợp chính xác với trường hợp này nhưng sẽ không phù hợp với tất cả các hành động khác tồn tại trên một tài nguyên nói chung. REST thậm chí có nghĩa vụ phải xử lý nó? Có lẽ tôi đang lạm dụng nó và chỉ nên sử dụng nó như CRUD và không có gì khác, như các truy vấn SQL mà tôi không mong đợi bất kỳ logic nào nằm trong chính truy vấn đó.
Miro Svrtan

Tại sao tôi hỏi tất cả những điều này? Vì thời gian trước, các ứng dụng web bắt đầu được đa nền tảng và tôi muốn giữ nhiều logic kinh doanh trên máy chủ kể từ khi cập nhật logic kinh doanh trên iOS, Android, web, máy tính để bàn hoặc bất kỳ nền tảng nào khác xuất hiện là điều không thể làm được một cách nhanh chóng và tôi muốn tránh tất cả các vấn đề về khả năng tương thích ngược khi thay đổi một số BL nhỏ.
Miro Svrtan

2
Tôi nghĩ REST có thể xử lý logic kinh doanh tốt, nhưng nó không phù hợp để phơi bày logic kinh doanh hiện tại được viết mà không có REST trong tâm trí. Đây là lý do tại sao nhiều công ty như Microsoft, SAP và các công ty khác thường chỉ hiển thị dữ liệu với các hoạt động CRUD, giống như bạn đã nói. Có một cái nhìn quảng cáo Giao thức dữ liệu mở để xem cách họ làm điều đó.
Ferenc Mihaly

7

chúng tôi cần gửi tất cả dữ liệu bài viết trở lại API để cập nhật và công việc nhiều người dùng không thể thực hiện được. Ví dụ, trình chỉnh sửa có thể gửi dữ liệu cũ hơn 5 giây và ghi đè lên bản sửa lỗi mà một số nhà báo khác mới làm cách đây 2 giây và tôi không thể giải thích cho khách hàng điều này vì những bài báo đó thực sự không liên quan đến việc cập nhật nội dung.

Loại vấn đề này là một thách thức cho dù bạn có làm gì đi chăng nữa, Đó là một vấn đề rất giống với kiểm soát nguồn phân tán (mercurial, git, v.v.) và giải pháp, được đánh vần bằng HTTP / ReST, trông hơi giống nhau.

Giả sử bạn đã có hai người dùng, Alice và Bob, cả hai đều làm việc /articles/lunch. (để rõ ràng, phản ứng là trong khuôn mặt táo bạo)

Đầu tiên, alice tạo ra bài viết.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

Máy chủ không tạo tài nguyên, vì không có "phiên bản" nào được đính kèm với yêu cầu, (giả sử là định danh của /articles/{id}/{version}. Để thực hiện việc tạo, Alice đã được chuyển hướng đến url của bài viết / phiên bản mà cô ấy sẽ tạo. Đại lý sau đó sẽ áp dụng lại yêu cầu tại địa chỉ mới.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

Và bây giờ bài viết đã được tạo ra. tiếp theo, bob nhìn vào bài báo:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Bob nhìn ở đó:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

Anh quyết định thêm sự thay đổi của chính mình.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

Như với Alice, Bob được chuyển hướng đến nơi anh sẽ tạo ra một phiên bản mới.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Cuối cùng, Alice quyết định rằng cô muốn thêm vào bài viết của mình:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

Thay vì được chuyển hướng như bình thường, một mã trạng thái khác được trả về máy khách, thông báo 409cho Alice rằng phiên bản mà cô đang cố gắng phân nhánh đã được phân nhánh. Các tài nguyên mới được tạo ra bằng mọi cách (như được hiển thị bởi Locationtiêu đề) và sự khác biệt giữa hai tài nguyên được bao gồm trong phần phản hồi. Alice bây giờ biết rằng yêu cầu cô vừa thực hiện cần phải được hợp nhất một số cách.


Tất cả các chuyển hướng này có liên quan đến ngữ nghĩa của PUT, đòi hỏi các tài nguyên mới được tạo chính xác nơi dòng yêu cầu yêu cầu. điều này cũng có thể lưu chu kỳ yêu cầu bằng cách sử dụng POSTthay vào đó, nhưng sau đó số phiên bản sẽ phải được mã hóa theo yêu cầu bởi một số phép thuật khác, điều này dường như ít rõ ràng hơn đối với mục đích minh họa, nhưng có lẽ vẫn được ưa thích trong API thực để giảm thiểu chu kỳ yêu cầu / đáp ứng.


1
Versoning không phải là một vấn đề ở đây, tôi chỉ nói đó là ví dụ về các vấn đề có thể xảy ra nếu sử dụng bài viết làm tài nguyên cho hành động xuất bản
Miro Svrtan

3

Đây là một ví dụ khác không liên quan đến nội dung tài liệu mà nhiều hơn với trạng thái thoáng qua. (Tôi tìm thấy phiên bản - nói chung, nói chung, mỗi phiên bản có thể là một tài nguyên mới - một loại vấn đề dễ dàng.)

Giả sử tôi muốn hiển thị một dịch vụ đang chạy trên máy thông qua REST để có thể dừng, khởi động, khởi động lại, v.v.

Cách tiếp cận RESTful nhất ở đây là gì? POST / dịch vụ? Lệnh = khởi động lại, ví dụ? Hoặc POST / dịch vụ / trạng thái với phần thân là 'chạy'?

Thật tốt khi mã hóa các thực tiễn tốt nhất ở đây và liệu REST có phải là phương pháp phù hợp với loại tình huống này hay không.

Thứ hai, giả sử tôi muốn điều khiển một số hành động từ một dịch vụ không ảnh hưởng đến trạng thái của chính nó, nhưng lại gây ra tác dụng phụ. Ví dụ: dịch vụ gửi thư gửi báo cáo, được xây dựng tại thời điểm gọi đến một loạt địa chỉ email.

NHẬN / báo cáo có thể là một cách để có được một bản sao của báo cáo; nhưng điều gì sẽ xảy ra nếu chúng ta muốn đẩy sang phía máy chủ các hành động tiếp theo như gửi email như tôi nói ở trên. Hoặc viết vào cơ sở dữ liệu.

Các trường hợp này nhảy xung quanh sự phân chia hành động tài nguyên và tôi thấy các cách xử lý chúng theo cách định hướng REST, nhưng thật lòng mà nói, nó cảm thấy giống như một chút hack để làm như vậy. Có lẽ câu hỏi chính là liệu API REST có hỗ trợ các tác dụng phụ nói chung hay không.


2

REST được định hướng dữ liệu và vì các tài nguyên đó hoạt động tốt nhất như "những thứ" không phải là hành động. Các ngữ nghĩa ngầm của các phương thức http; NHẬN, PUT, XÓA, vv phục vụ để củng cố định hướng. POST tất nhiên, là ngoại lệ.

Một tài nguyên có thể là một hỗn hợp của dữ liệu tức là. nội dung bài viết; và siêu dữ liệu tức là. xuất bản, khóa, sửa đổi. Có nhiều cách khác có thể để cắt dữ liệu, nhưng bạn phải xem qua luồng dữ liệu sẽ trông như thế nào trước tiên để xác định cách tối ưu nhất (nếu có). Ví dụ, có thể các bản sửa đổi phải là tài nguyên của riêng họ trong bài viết như TokenMacGuy gợi ý.

Về việc thực hiện tôi có thể sẽ làm một cái gì đó giống như những gì TockenMacGuy gợi ý. Tôi cũng sẽ thêm một trường siêu dữ liệu vào bài viết, không phải sửa đổi, như 'bị khóa' và 'được xuất bản'.


1

Đừng nghĩ về nó như trực tiếp thao túng trạng thái của bài viết. Thay vào đó, bạn đang đặt một thứ tự thay đổi yêu cầu bài viết được tạo.

Bạn có thể mô hình hóa việc đặt theo thứ tự thay đổi là tạo tài nguyên thứ tự thay đổi mới (POST). Có rất nhiều lợi thế. Ví dụ: bạn có thể chỉ định ngày và thời gian trong tương lai khi bài viết sẽ được xuất bản như một phần của thứ tự thay đổi và để máy chủ lo lắng về cách thực hiện.

Nếu xuất bản Không phải là một quy trình tức thời, bạn không phải đợi nó kết thúc trước khi quay lại máy khách. Bạn chỉ cần xác nhận rằng lệnh thay đổi đã được tạo và trả về ID lệnh thay đổi. Sau đó, bạn có thể sử dụng URL tương ứng với thứ tự thay đổi đó để chia sẻ trạng thái của thứ tự thay đổi.

Một cái nhìn sâu sắc quan trọng đối với tôi là nhận ra phép ẩn dụ thứ tự thay đổi này chỉ là một cách khác để mô tả lập trình hướng đối tượng. Thay vì tài nguyên, chúng ta gọi các đối tượng. Thay vì thay đổi đơn đặt hàng, chúng tôi gọi cho họ tin nhắn. Một cách để gửi tin nhắn từ A đến B trong OO là yêu cầu A gọi một phương thức trên B. Một cách khác để thực hiện, đặc biệt khi A và B ở các máy tính khác nhau, là để A tạo một đối tượng mới, M và gửi nó đến B. REST chỉ cần chính thức hóa quy trình đó.


Tôi thực sự xem xét điều này gần với mô hình Diễn viên hơn OO. Việc xem xét các yêu cầu REST dưới dạng Tin nhắn (chính là chúng) sắp xếp chúng rất gọn gàng với Nguồn tìm sự kiện để quản lý trạng thái. REST phân chia gọn gàng các tương tác của nó dọc theo các đường CQRS. Tin nhắn GET là Truy vấn, POST, PUT, PATCH, DELETE rơi vào vùng Command.
WillD

0

Nếu tôi hiểu bạn một cách chính xác, tôi nghĩ rằng những gì bạn có là vấn đề xác định 'quy tắc kinh doanh' hơn là vấn đề kỹ thuật.

Việc một bài viết có thể được ghi đè có thể được giải quyết bằng cách đưa ra các cấp ủy quyền nơi người dùng cao cấp có thể ghi đè các phiên bản của người dùng cơ sở. Ngoài ra, bằng cách giới thiệu các phiên bản cũng như một cột để nắm bắt trạng thái của bài viết (ví dụ: 'đang phát triển', 'cuối cùng' , v.v.), bạn có thể khắc phục điều này. Bạn cũng có thể cung cấp cho người dùng khả năng chọn một phiên bản nhất định bằng cách kết hợp thời gian gửi và theo số phiên bản.

Trong tất cả các trường hợp trên, dịch vụ của bạn cần thực hiện các quy tắc kinh doanh bạn đặt. Vì vậy, bạn có thể gọi dịch vụ với các tham số: userid, bài viết, phiên bản, hành động (trong đó phiên bản là tùy chọn, một lần nữa điều này phụ thuộc vào quy tắc kinh doanh của bạn).


Tôi không tin rằng đây là một quy tắc kinh doanh nhưng hoàn toàn là một quy tắc công nghệ. Ý tưởng với việc thêm phiên bản là một ý tưởng tốt để trợ giúp cho các quy tắc ghi đè nhưng vẫn không giải quyết được tình huống cập nhật nội dung và xuất bản nội dung không phải là hành động liên quan.
Miro Svrtan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.