Thiết kế API RESTful dựa trên chức năng


8

Hãy giải quyết một cuộc tranh cãi giữa tôi và một người bạn.

Chúng tôi hiện đang thiết kế API sản phẩm. Thực thể sản phẩm của chúng tôi trông như thế này

{
    "Id": "",
    "ProductName": "",
    "StockQuantity": 0
}

Bán sản phẩm được xử lý bởi bên thứ 3 và họ có nghĩa vụ phải thông báo cho chúng tôi số lượng đã mua để StockQuantitycó thể giảm trường.

Cách tiếp cận của tôi:

PUT /api/Product/{Id}/ --data { "StockQuantity": "{NewStockQuantity}" }

Bên thứ 3 chịu trách nhiệm truy vấn sản phẩm, thực hiện tính toán dựa trên StockQuantitysố lượng hiện tại và số lượng đã mua và gửi PUTyêu cầu với giá trị mới.

Bạn tôi không muốn bên thứ 3 thực hiện phép tính. Cách tiếp cận của anh ấy

PUT /api/Product/{Id}/DecreaseStock --data { "PurchasedQuantity": "{PurchasedQuantity}" }

Vì vậy, chúng tôi có thể thực hiện tính toán và cập nhật StockQuantity

Tôi không muốn tạo các điểm cuối dựa trên chức năng và anh ấy không muốn tin tưởng vào bên thứ 3 để thực hiện các phép tính.

Điều gì sẽ là cách chính xác để chúng ta tiếp cận vấn đề này?


Hãy nhớ rằng PUT phải là (về lý thuyết) idempotent. Lựa chọn 1 sẽ phù hợp với ngữ nghĩa. Lựa chọn 2 sẽ không. Nếu để tuân thủ REST là quan trọng đối với bạn. Bạn sẽ cố gắng giữ các cuộc gọi PUT bình thường vì nó sẽ giúp bạn tránh khỏi rất nhiều vấn đề đau đầu. Tương tự cho XÓA. Đối với các hoạt động giống như lệnh, thành thật mà nói, tôi sẽ trao cơ hội cho RPC Json hoặc XML. Cả hai chiến lược (REST và RPC) có thể sống cùng nhau trong cùng một API Web. Nó truyền đạt theo nguyên tắc CQRS (cách ly trách nhiệm truy vấn lệnh :-)
Laiv

Câu trả lời:


19

Bạn có thể để bên thứ 3 đăng bán hàng cho sản phẩm của bạn. Ví dụ:

POST /product/{id}/sale { "Quantity": 3 }

Tôi đồng ý với quan điểm của cả bạn và đồng nghiệp của bạn. Đây là logic nghiệp vụ và không nên để lại cho máy khách API, nhưng bạn cũng nên tránh có "chức năng" làm điểm cuối.

Đôi khi giải quyết các vấn đề như vậy dễ dàng như cách gọi nó khác nhau, thừa nhận không phải lúc nào.


2
Điều này. Plus: có vẻ như mỗi lần bán cũng cần một đối tượng trên cơ sở dữ liệu. Có mỗi lần bán dưới dạng đối tượng riêng biệt trong db cho phép truy xuất nguồn gốc. Hãy suy nghĩ, nếu có gì đó không ổn và số lượng chứng khoán cuối cùng là sai và cần sửa giá trị. Nếu bạn chỉ có một cột giá trị cuối cùng thì bạn không thể làm được gì nhiều. Hy vọng rằng có bất kỳ nhật ký hữu ích trong hệ thống để tìm ra những gì đã sai. Nếu bạn có các đối tượng bán có dấu thời gian, tên người dùng và có thể cả địa chỉ IP được đính kèm, bạn có khả năng xóa một số bản ghi để sửa dữ liệu và theo dõi người dùng / vị trí đó đến từ đâu.
Trượt tuyết

Cảm ơn bạn cho đầu vào. Bán / Đặt hàng là tài nguyên của một nhóm khác, tôi không có trách nhiệm lưu hoặc xử lý chúng. Biết điều này, là tạo /saleđiểm cuối vẫn còn hiệu lực?
Sefa Ümit Oray

@ SefaÜmitOray: Các điểm cuối /sale/product/{id}/salehoàn toàn độc lập và thực tế là chúng có tên tương tự không có nghĩa là chúng đề cập đến cùng một tài nguyên.
Bart van Ingen Schenau

@BartvanIngenSchenau Ý tôi là, salekhông thuộc miền của tôi và nó không phải là một phần của product. Liệu nó vẫn có ý nghĩa để tạo ra /product/{id}/saletrong khi nó không đại diện cho bất kỳ tài nguyên thực tế?
Sefa Ümit Oray

5
@ SefaÜmitOray Hoàn toàn hợp lệ nếu nó đại diện cho điều gì đó có ý nghĩa trong ngữ cảnh của bạn. Nó không có nghĩa tương tự như trong các bối cảnh khác và nó cũng không phải là bất cứ điều gì được duy trì trực tiếp cho cơ sở dữ liệu. Tên miền! = Bảng cơ sở dữ liệu, Tài nguyên! = Bảng cơ sở dữ liệu.
Robert Bräutigam

3

Không có lý do gì mà bạn không thể làm được; hoặc cả hai.

Trong một điểm của bối cảnh bán hàng, theo dõi các giao dịch cá nhân có rất nhiều ý nghĩa. Ở đó, giải pháp của Robert rất có ý nghĩa.

Trong bối cảnh kho / kho, bạn không nhất thiết phải theo dõi các giao dịch nhiều như "lấy hàng tồn kho"; có một điểm cuối cho phép khách hàng báo cáo mức cổ phiếu của họ

Tôi có 10 đơn vị Tôi có 7 đơn vị Tôi có 3 đơn vị Tôi có 20 đơn vị

Hãy làm cho nó thêm ý nghĩa hơn.

Mức cổ phiếu thay đổi vì những lý do khác ngoài "doanh số"; Chỉ là một thứ để ghi nhớ trong đầu.

Về lý thuyết, mức độ chứng khoán nên được tính toán từ những thay đổi; nhưng trong một số lĩnh vực chính xác là giả định mà bạn muốn xác minh . Bạn sẽ muốn có thể tính toán mức chứng khoán theo hai cách khác nhau và kiểm tra sự khác biệt (hay còn gọi là "co rút").

Vì vậy, tôi không nghĩ rằng ngữ nghĩa là rõ ràng, dựa trên bối cảnh bạn đã cung cấp.

Về phần HTTP; PUT [target-uri]có ý nghĩa về mặt ngữ nghĩa khi bạn thay thế một đại diện của một tài liệu bằng một tài liệu khác. Đó là UPSERT- PUT thứ hai cho tài nguyên đang yêu cầu ghi đè lên biểu diễn hiện có.

PUT /sales { Quantity = 5 }
PUT /sales { Quantity = 2 }
PUT /sales { Quantity = 3 }

nói rằng số lượng đơn vị bán là 3không 10.

PUT /sales/1 { Quantity = 5 }
PUT /sales/2 { Quantity = 2 }
PUT /sales/3 { Quantity = 3 }

Đó là những gì 10trông giống như

PUT /sales { Quantity : [5] }
PUT /sales { Quantity : [5,2] }
PUT /sales { Quantity : [5,2,3] }

Đó là một cách đánh vần khác 10.

POST /sales { Quantity = 5 }
POST /sales { Quantity = 2 }
POST /sales { Quantity = 3 }

Theo như HTTP có liên quan, điều này cũng được chấp nhận. Tuy nhiên, đó không phải là một lựa chọn tuyệt vời trên một mạng không đáng tin cậy vì các thông điệp đôi khi bị trùng lặp.

POST /sales { Quantity = 5 }
POST /sales { Quantity = 2 }
POST /sales { Quantity = 3 }
POST /sales { Quantity = 3 }

Có phải vậy 13không? hay 10?

PUT /sales/1 { Quantity = 5 }
PUT /sales/2 { Quantity = 2 }
PUT /sales/3 { Quantity = 3 }
PUT /sales/3 { Quantity = 3 }

Điều đó rõ ràng 10

PUT /sales { Quantity : [5,2,3] }
PUT /sales { Quantity : [5,2,3] }

Điều đó rõ ràng 10

PUT /sales/1 { Quantity = 5 }
PUT /sales/2 { Quantity = 2 }
PUT /sales/3 { Quantity = 3 }
PUT /sales/4 { Quantity = 3 }

Đó là rõ ràng 13

PUT /sales { Quantity : [5,2,3] }
PUT /sales { Quantity : [5,2,3,3] }

Đó là rõ ràng 13

POST /sales { TransactionId = 1 , Quantity = 5 }
POST /sales { TransactionId = 2 , Quantity = 2 }
POST /sales { TransactionId = 3 , Quantity = 3 }
POST /sales { TransactionId = 3 , Quantity = 3 }

10

POST /sales { TransactionId = 1 , Quantity = 5 }
POST /sales { TransactionId = 2 , Quantity = 2 }
POST /sales { TransactionId = 3 , Quantity = 3 }
POST /sales { TransactionId = 4 , Quantity = 3 }

13

(Công bằng mà nói, HTTP có hỗ trợ cho các yêu cầu có điều kiện ; bạn có thể nâng một số siêu dữ liệu từ giao thức cụ thể miền của mình vào các tiêu đề bất khả tri của miền để loại bỏ một số sự mơ hồ - nếu bạn có thể thuyết phục khách hàng chơi cùng).

Tất nhiên, có sự đánh đổi - HTML không có hỗ trợ PUT riêng; nếu bạn dự định các ứng dụng khách API của mình sẽ trở thành trình duyệt, thì bạn cần một giao thức dựa trên POST hoặc bạn cần các tiện ích mở rộng mã theo yêu cầu để chuyển đổi biểu mẫu gửi từ POST sang PUT.


1
Bạn không phải theo dõi doanh số bán hàng riêng lẻ, chỉ vì có một điểm cuối cho nó. Tức là không cần phải có thể liệt kê các cuộc gọi bán hàng trước đó, chỉ vì bạn có thể POST cho nó. Tuy nhiên, bạn đúng, có thể có các trường hợp sử dụng khác (chúng tôi không biết) và bạn nên xác định các cuộc gọi tạm thời bằng các cuộc gọi có điều kiện hoặc với một số phương tiện khác.
Robert Bräutigam

2

Đây có vẻ như là một thiết kế thực sự tồi tệ cho dù bạn cắt nó như thế nào. Tôi sẽ không bao giờ tin tưởng một bên thứ ba nói với hàng tồn kho hiện tại trừ khi tôi thuê họ để quản lý kho của tôi.

Hơn nữa, cách tiếp cận tìm kiếm chức năng hoàn toàn không phải là RESTful và bị ràng buộc để tạo ra sự phân biệt giữa những người tiêu dùng của bạn.

Cuối cùng, tôi không thể tưởng tượng ra một kịch bản trong đó điều duy nhất bạn quan tâm về việc bán hàng là hàng tồn kho kết quả bạn còn lại sau khi hoàn thành.

Bạn sẽ tốt hơn rất nhiều khi bên thứ ba gửi tài nguyên Bán hàng hoặc Hóa đơn cho bạn (với thông tin như sản phẩm, số lượng, ngày, phương thức giao hàng, thông tin khách hàng, v.v.). Điều này cho phép bạn thực sự phân tích và theo dõi thực sự những gì bạn đang bán, cho ai, khi nào, v.v. để bạn có thể thực hiện việc kinh doanh của mình.

Ngay cả khi bên thứ ba của bạn đang thực hiện tổng đơn hàng, bạn sẽ muốn theo dõi doanh số cho mục đích nhân khẩu học và kế toán nếu không có gì khác.


1

PUT / api / Sản phẩm / {Id} / --data {"StockQuantity": "{NewStockQuantity}"}

Kiểu thiết kế này có một vấn đề lớn ở chỗ nếu bạn muốn có nhiều hơn một luồng khách chạy với API của mình, bạn có thể bị đọc / ghi bẩn. Nghĩa là, giữa thời gian khách hàng giảm số lượng hiện tại và tính giá trị mới, một khách hàng khác có thể kéo cùng giá trị trước đó và tính một câu trả lời khác nhau. Số lượng bạn kết thúc sẽ là bất kỳ số nào cập nhật cuối cùng nhưng không chính xác. Ví dụ: số lượng hiện tại của bạn là 10. Khách hàng A muốn bán 5 mặt hàng và kéo số lượng hiện tại. Đồng thời, khách hàng B muốn bán 6 mặt hàng và kéo số lượng hiện tại. Cả hai nhìn thấy 10 mặt hàng trong kho. A tính 5 mục còn lại. Btính 4 số còn lại. Cả hai cập nhật. Bây giờ bạn hiển thị 4 hoặc 5 mục còn lại tùy thuộc vào bản cập nhật của ai được ghi lại lần cuối. Tuy nhiên, bạn thực sự đã bán nhiều mặt hàng mà bạn thực sự có. Điều tồi tệ hơn là không có cách dễ dàng để đi qua và xem những gì đã sai. Tất cả bạn có là hai không chính xác PUTstrong nhật ký của bạn để xem xét.

Trong bất kỳ hệ thống hồ sơ trong thế giới thực, chỉ cần có tổng số hiện tại là không đủ. Hãy xem xét nếu bạn đến một cửa hàng và mua một số mặt hàng. Bạn yêu cầu một biên lai và nhân viên thu ngân chỉ đưa cho bạn một phiếu với tổng số tiền duy nhất trên đó. Làm thế nào bạn sẽ hiển thị tổng số là chính xác từ biên lai đó? Làm thế nào bạn sẽ cho thấy bạn đã mua một mặt hàng nếu bạn muốn trả lại một cái gì đó?

Cách tiếp cận của bạn của bạn tốt hơn nhưng tôi khuyên bạn nên thêm id giao dịch vào hỗn hợp. Điều này giải quyết mối quan tâm thực sự mà VoiceOfUnreason đề cập về các giao dịch trùng lặp. Một lựa chọn là cung cấp một POSThoạt động để tạo ra một giao dịch mới và sau đó PUTđến giao dịch đó để xác nhận nó. Tại thời điểm xác nhận, bạn giảm tổng số cổ phiếu hoặc từ chối yêu cầu vì không có đủ.


1

Vì doanh số được xử lý bởi bên thứ 3, bạn phải kiểm soát hàng tồn kho sản phẩm của mình bằng cách không cho phép họ cập nhật số lượng hàng tồn kho.

Để sử dụng nội bộ, ví dụ như mục đích đếm cổ phiếu, bạn có thể có cách tiếp cận của mình, tức là PUT /api/Product/{Id}/ --data { "StockQuantity": "{NewStockQuantity}" }.

Để sử dụng bên ngoài, bạn phải tạo một giao diện riêng, ví dụ: /api/SalesOrder/có một danh sách các sản phẩm và số lượng, như:

POST /api/SalesOrder/ --data { [{"Id": 1, "Qty": 1}, {"Id": 2, "Qty": 3}] }

Dựa trên việc SalesOrdergửi bởi bên thứ 3, số lượng của mỗi sản phẩm có thể được cập nhật và gán cho đơn đặt hàng hoặc bạn có thể từ chối đơn đặt hàng nếu không có đủ sản phẩm.

Việc xử lý và đếm cổ phiếu là quy trình nội bộ, bên thứ 3 chỉ yêu cầu giao diện để họ có thể chuyển tiếp đơn hàng của mình để kiểm kê. Về cơ bản, SalesOrdercách thức Bán hàng, Tài chính và Kho giao tiếp để hoàn thành việc bán hàng.

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.