Triển khai mẫu lệnh trong API RESTful


12

Tôi đang trong quá trình thiết kế API HTTP, hy vọng làm cho nó trở nên RESTful nhất có thể.

Có một số hành động mà chức năng trải rộng trên một vài tài nguyên và đôi khi cần phải hoàn tác.

Tôi tự nghĩ, điều này nghe giống như một mẫu lệnh, nhưng làm thế nào tôi có thể mô hình hóa nó thành một tài nguyên?

Tôi sẽ giới thiệu một tài nguyên mới có tên XXAction, như DepositAction, sẽ được tạo thông qua một cái gì đó như thế này

POST /card/{card-id}/account/{account-id}/Deposit
AmountToDeposit=100, different parameters...

điều này thực sự sẽ tạo một DepositAction mới và kích hoạt phương thức Do / Execute. Trong trường hợp này, trả về trạng thái HTTP được tạo 201 có nghĩa là hành động đã được thực hiện thành công.

Sau này nếu khách hàng muốn xem chi tiết hành động anh ta có thể

GET /action/{action-id}

Tôi đoán cập nhật / PUT sẽ bị chặn, vì nó không liên quan ở đây.

Và để hoàn tác hành động, tôi nghĩ đến việc sử dụng

DELETE /action/{action-id}

mà thực sự sẽ gọi phương thức Hoàn tác của đối tượng có liên quan và thay đổi trạng thái của nó.

Hãy nói rằng tôi hạnh phúc chỉ với một Do-Undo, tôi không cần Làm lại.

Cách tiếp cận này có ổn không?

Có bất kỳ cạm bẫy, lý do không sử dụng nó?

Điều này có được hiểu từ POV của khách hàng không?


Câu trả lời ngắn, đó không phải là REST.
Evan Plaice

3
@EvanPlaice quan tâm đến công phu về điều đó? đó chính xác là câu hỏi
Mithir

1
Tôi đã có thể giải thích trong một câu trả lời nhưng câu trả lời của Gary đã bao gồm hầu hết / tất cả những gì tôi thêm vào. Tôi nói rằng nó không nghỉ ngơi vì URI chỉ được coi là đại diện cho tài nguyên (tức là không phải hành động). Các hành động được xử lý thông qua GET / POST / PUT / DELETE / Head. Hãy nghĩ về REST như một giao diện OOP. Mục tiêu là làm cho API phù hợp với mô hình chung và tách nó khỏi các chi tiết cụ thể thực hiện nhất có thể.
Evan Plaice

1
@EvanPlaice Ok tôi hiểu rồi, cảm ơn. Tôi nghĩ nó khó hiểu ở đây vì Tiền gửi có thể được coi là một danh từ và một động từ ...
Mithir

Trong trường hợp này, URI nên thể hiện một giao dịch trong đó ghi nợ (lấy tiền) và ghi có (đưa tiền) là các hành động được thực hiện thông qua các yêu cầu POST. POST được sử dụng cho cả hai vì mỗi lần tiền được di chuyển theo một trong hai hướng, nó thể hiện một giao dịch mới được tạo. Trong trường hợp cụ thể của bạn, các giao dịch đang diễn ra trên tài khoản của chủ thẻ, vì vậy số tài khoản của thẻ là URI tài nguyên.
Evan Plaice

Câu trả lời:


13

Bạn đang thêm vào một lớp trừu tượng gây nhầm lẫn

API của bạn bắt đầu rất sạch sẽ và đơn giản. POST HTTP tạo một tài nguyên Tiền gửi mới với các tham số đã cho. Sau đó, bạn đi ra khỏi đường ray bằng cách giới thiệu ý tưởng về "hành động" là một chi tiết triển khai chứ không phải là một phần cốt lõi của API.

Thay vào đó, hãy xem xét cuộc trò chuyện HTTP này ...

POST / thẻ / {card-id} / tài khoản / {tài khoản-id} / Gửi tiền

Số lượng ToDeposit = 100, các tham số khác nhau ...

201 TẠO

Vị trí = / thẻ / 123 / tài khoản / 456 / Tiền gửi / 789

Bây giờ bạn muốn hoàn tác thao tác này (về mặt kỹ thuật không nên cho phép điều này trong một hệ thống kế toán cân bằng nhưng điều này là gì):

XÓA / thẻ / 123 / tài khoản / 456 / Tiền gửi / 789

204 KHÔNG CÓ NỘI DUNG

Người tiêu dùng API biết rằng họ đang xử lý tài nguyên Tiền gửi và có thể xác định những hoạt động nào được phép trên đó (thường thông qua TÙY CHỌN trong HTTP).

Mặc dù việc triển khai thao tác xóa được thực hiện thông qua "hành động" ngày nay, không có gì đảm bảo rằng khi bạn di chuyển hệ thống này từ, giả sử, C # sang Haskell và duy trì mặt trước rằng khái niệm phụ về "hành động" sẽ tiếp tục tăng giá trị , trong khi khái niệm chính về Tiền gửi chắc chắn có.

Chỉnh sửa để bao gồm một giải pháp thay thế cho XÓA và Gửi tiền

Để tránh thao tác xóa nhưng vẫn xóa hiệu quả Khoản tiền gửi, bạn nên thực hiện các thao tác sau (sử dụng Giao dịch chung để cho phép Gửi tiền và rút tiền):

POST / thẻ / {card-id} / tài khoản / {tài khoản-id} / Giao dịch

Số tiền = -100 , các thông số khác nhau ...

201 TẠO

Vị trí = / thẻ / 123 / tài khoản / 456 / Chuyển đổi / 790

Tài nguyên Giao dịch mới được tạo có số tiền chính xác (-100). Điều này có tác dụng cân bằng tài khoản về 0, phủ nhận Giao dịch gốc.

Bạn có thể xem xét việc tạo điểm cuối "tiện ích" như

POST / thẻ / {card-id} / tài khoản / {tài khoản-id} / Giao dịch / 789 / Hoàn tác <- BAD!

để có được hiệu quả tương tự. Tuy nhiên, điều này phá vỡ ngữ nghĩa của một URI như là một định danh bằng cách giới thiệu một động từ. Bạn tốt hơn hết là bám vào các danh từ trong định danh và giữ cho các hoạt động bị ràng buộc với các động từ HTTP. Bằng cách đó, bạn có thể dễ dàng tạo một permalink từ mã định danh và sử dụng nó cho GET và vv.


3
+1 "về mặt kỹ thuật không nên cho phép điều này trong một hệ thống kế toán cân bằng". Ai đó biết đếm đậu. Tuyên bố đó là hoàn toàn chính xác, cách để đảo ngược sẽ là tạo ra một giao dịch khác ghi có tiền vào lại. Các mục sổ kế toán tổng hợp phải luôn được coi là bất biến và vĩnh viễn sau khi giao dịch được hoàn thành.
Evan Plaice

Vì vậy, nếu tôi thay đổi, trong các câu hỏi của tôi, thay vì Xóa / hành động / ... thành Xóa / ký gửi / ... thì có ổn không?
Mithir

2
@Mithir Tôi đã mô tả quy tắc kế toán. Trong một hệ thống sổ sách kế toán kép tiêu chuẩn, bạn không bao giờ xóa giao dịch. Lịch sử một khi đã cam kết được coi là bất biến để giữ cho mọi người trung thực. Trong trường hợp của bạn, bạn vẫn có thể sử dụng hành động XÓA nhưng trên back-end (bảng cơ sở dữ liệu sổ cái chung), bạn sẽ thêm một giao dịch khác thể hiện việc ghi có (tức là trả lại) tiền cho người dùng. Tôi không có quầy bán đậu (tức là kế toán viên) nhưng đó là một trong những thực hành tiêu chuẩn được dạy trong khóa học "Nguyên tắc kế toán I".
Evan Plaice

2
(tiếp) Nhật ký cơ sở dữ liệu sử dụng các giao dịch theo cách tương tự. Đó là lý do tại sao có thể sao chép và / hoặc xây dựng lại tập dữ liệu chỉ bằng các bản ghi. Miễn là các giao dịch được phát lại theo trình tự thời gian, có thể xây dựng lại bộ dữ liệu từ bất kỳ điểm nào trong lịch sử của nó. Loại bỏ tính đột biến khỏi phương trình đảm bảo tính nhất quán.
Evan Plaice

1
Đủ công bằng chỉ cần đổi tên nó thành Giao dịch.
Gary Rowe

1

Lý do chính cho sự tồn tại của REST là khả năng phục hồi chống lại các lỗi mạng. Để kết thúc tất cả các hoạt động nên idempotent .

Cách tiếp cận cơ bản có vẻ hợp lý, nhưng cách bạn mô tả DepositActionsáng tạo không có vẻ gì là bình thường, cần được sửa chữa. Bằng cách khách hàng cung cấp ID duy nhất sẽ được sử dụng để phát hiện các yêu cầu trùng lặp. Vì vậy, sự sáng tạo sẽ thay đổi thành

PUT /card/{card-id}/account/{account-id}/Deposit/{action-id}
AmountToDeposit=100, different parameters...

Nếu một PUT khác cho cùng một URL được thực hiện với cùng một nội dung như trước đó, thì phản hồi vẫn phải là 201 created nếu nội dung giống nhau và lỗi nếu nội dung khác nhau. Điều này cho phép khách hàng chỉ cần truyền lại yêu cầu khi không thành công, vì khách hàng không thể biết liệu yêu cầu hoặc phản hồi có bị mất hay không.

Nó có ý nghĩa hơn khi sử dụng PUT, bởi vì nó chỉ ghi tài nguyên và không có gì, nhưng sử dụng POST cũng không thực sự gây ra bất kỳ vấn đề nào.

Để xem chi tiết giao dịch, khách hàng sẽ GETsử dụng cùng một URL, nghĩa là

GET /card/{card-id}/account/{account-id}/Deposit/{action-id}

và để hoàn tác nó, nó có thể XÓA nó. Nhưng nếu nó thực sự có liên quan đến tiền như mẫu gợi ý, tôi sẽ đề nghị PUTting nó với các cờ "bị hủy" thay vì trách nhiệm giải trình (rằng vẫn còn dấu vết của giao dịch được tạo và hủy).

Bây giờ bạn cần chọn một phương thức tạo id duy nhất. Bạn có một vài lựa chọn:

  1. Phát hành tiền tố dành riêng cho khách hàng trước đó trong quá trình trao đổi phải được đưa vào.
  2. Thêm một yêu cầu POST đặc biệt để nhận ID duy nhất trống từ máy chủ. Yêu cầu này không phải là bình thường (và thực sự không thể), vì ID không được sử dụng không thực sự gây ra bất kỳ rắc rối nào.
  3. Đơn giản chỉ cần sử dụng UUID. Mọi người đều sử dụng chúng và dường như không ai có vấn đề gì với cả những người dựa trên MAC cũng như những người ngẫu nhiên.

2
Từ những gì tôi biết, POST không phải là idempotent. vi.wikipedia.org/wiki/POST_(HTTP)#Affecting_server_state
Mithir

@Mithir: POST không được coi là idempotent; nó vẫn có thể Nhưng sự thật là vì tất cả các hoạt động REST được coi là không hoạt động, POST về cơ bản không có chỗ trong REST.
Jan Hudec

1
Tôi bối rối ... nội dung tôi đã đọc và triển khai hiện có Tôi quen thuộc (ServiceStack, ASP.NET Web API), tất cả đều gợi ý rằng POST có một vị trí trong REST.
Mithir

3
Trong idempotence được gán cho tài nguyên, không phải giao thức hoặc mã phản hồi của nó. Do đó, trong REST qua HTTP, các phương thức GET, PUT, DELETE, PATCH, v.v được coi là idempotent mặc dù mã phản hồi của chúng có thể thay đổi cho các cuộc gọi tiếp theo. POST là idempotent theo nghĩa là mỗi cuộc gọi tạo ra một tài nguyên mới. Xem Fielding's Bạn có thể sử dụng POST .
Gary Rowe

1
Các hoạt động không bình thường được cho phép trong phần còn lại. Sự khẳng định đó là sai lầm.
Andy
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.