Làm cách nào để thiết kế tìm kiếm / lọc RESTful? [đóng cửa]


457

Tôi hiện đang thiết kế và triển khai API RESTful trong PHP. Tuy nhiên, tôi đã không thành công khi thực hiện thiết kế ban đầu của mình.

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

Cho đến nay khá chuẩn, phải không?

Vấn đề của tôi là với cái đầu tiên GET /users. Tôi đã xem xét việc gửi các tham số trong cơ thể yêu cầu để lọc danh sách. Điều này là do tôi muốn có thể chỉ định các bộ lọc phức tạp mà không cần url siêu dài, như:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

Thay vào đó tôi muốn có một cái gì đó như:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

dễ đọc hơn nhiều và cung cấp cho bạn các khả năng tuyệt vời để đặt các bộ lọc phức tạp.

Dù sao, file_get_contents('php://input')đã không trả lại cơ thể GETyêu cầu cho các yêu cầu. Tôi cũng đã thử http_get_request_body(), nhưng lưu trữ chia sẻ mà tôi đang sử dụng không có pecl_http. Không chắc nó sẽ giúp được gì.

Tôi đã tìm thấy câu hỏi này và nhận ra rằng GET có lẽ không cần phải có một cơ quan yêu cầu. Đó là một chút không kết luận, nhưng họ khuyên chống lại nó.

Vì vậy, bây giờ tôi không biết phải làm gì. Làm thế nào để bạn thiết kế một chức năng tìm kiếm / lọc RESTful?

Tôi cho rằng tôi có thể sử dụng POST, nhưng điều đó không có vẻ rất RESTful.



60
Hãy cẩn thận!!! Phương thức GET phải là IDEMPOTENT và phải được "lưu vào bộ nhớ cache". Nếu bạn gửi thông tin trong cơ thể Làm thế nào hệ thống có thể lưu trữ yêu cầu của bạn? HTTP cho phép lưu trữ yêu cầu GET chỉ sử dụng URL, không phải phần thân yêu cầu. Ví dụ, điều này hai yêu cầu: example.com {test: "một số"} example.com {anotherTest: "some2"} được coi là giống nhau bởi hệ thống cache: Cả hai đều có giống hệt nhau URL
jfcorugedo

15
Chỉ cần thêm, bạn nên POST cho / users (bộ sưu tập) chứ không phải / user (một người dùng).
Mladen B.

1
Một điểm khác cần xem xét là hầu hết các máy chủ ứng dụng đều có nhật ký truy cập ghi nhật ký url và do đó có thể là bất cứ điều gì ở giữa. Vì vậy, có thể có một số rò rỉ thông tin không có chủ ý trên GET.
user3206144

2
Bản sao có thể có của thiết kế URL RESTful cho tìm kiếm
ivan_pozdeev

Câu trả lời:


397

Cách tốt nhất để thực hiện tìm kiếm RESTful là coi chính tìm kiếm đó là một tài nguyên. Sau đó, bạn có thể sử dụng động từ POST vì bạn đang tạo một tìm kiếm. Bạn không cần phải tạo một cái gì đó trong cơ sở dữ liệu để sử dụng POST.

Ví dụ:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

Bạn đang tạo một tìm kiếm từ quan điểm của người dùng. Các chi tiết thực hiện này là không liên quan. Một số API RESTful thậm chí có thể không cần sự kiên trì. Đó là một chi tiết thực hiện.


209
Một hạn chế đáng kể trong việc sử dụng yêu cầu POST cho điểm cuối tìm kiếm là nó không thể được đánh dấu. Đánh dấu kết quả tìm kiếm (truy vấn đặc biệt phức tạp) có thể khá hữu ích.
couchand

73
Sử dụng POST để thực hiện tìm kiếm có thể phá vỡ ràng buộc bộ đệm REST. whatisrest.com/rest_constraint/cache_excerps
Filipe

56
Các tìm kiếm, về bản chất, là nhất thời: dữ liệu tiến hóa giữa hai tìm kiếm có cùng tham số, vì vậy tôi nghĩ rằng một yêu cầu GET không ánh xạ rõ ràng vào mẫu tìm kiếm. Thay vào đó, yêu cầu tìm kiếm phải là POST (/ Resource / search), sau đó bạn có thể lưu tìm kiếm đó và chuyển hướng đến kết quả tìm kiếm, ví dụ / Resource / search / iyn3zrt. Bằng cách đó, NHẬN yêu cầu thành công và có ý nghĩa.
sleblanc

32
Tôi không nghĩ rằng bài đăng là phương pháp phù hợp để tìm kiếm, dữ liệu cho các yêu cầu GET thông thường cũng có thể thay đổi theo thời gian.
tự hỏi

82
Đây hoàn toàn là câu trả lời tồi tệ nhất có thể. Tôi không thể tin rằng nó có rất nhiều upvote. Câu trả lời này giải thích lý do tại sao: lập trình
viên.stackexchange.com

141

Nếu bạn sử dụng phần thân yêu cầu trong yêu cầu GET, bạn sẽ vi phạm nguyên tắc REST, vì yêu cầu GET của bạn sẽ không thể được lưu vào bộ đệm, vì hệ thống bộ đệm chỉ sử dụng URL.

Và điều tồi tệ hơn là URL của bạn không thể được đánh dấu, vì URL không chứa tất cả thông tin cần thiết để chuyển hướng người dùng đến trang này

Sử dụng tham số URL hoặc Truy vấn thay vì tham số thân yêu cầu.

ví dụ:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

Trên thực tế, HTTP RFC 7231 nói rằng:

Một tải trọng trong một thông báo yêu cầu GET không có ngữ nghĩa được xác định; gửi một cơ quan tải trọng theo yêu cầu GET có thể khiến một số triển khai hiện có từ chối yêu cầu.

Để biết thêm thông tin hãy xem tại đây


29
Học hỏi từ sai lầm của tôi - Tôi đã thiết kế một api bằng cách sử dụng đề xuất câu trả lời được chấp nhận (POSTing json), nhưng đang chuyển sang các tham số url. Khả năng đánh dấu có thể quan trọng hơn bạn nghĩ. Trong trường hợp của tôi, có nhu cầu hướng lưu lượng truy cập đến một số truy vấn tìm kiếm nhất định (chiến dịch quảng cáo). Ngoài ra, sử dụng API lịch sử có ý nghĩa hơn với các tham số URL.
Jake

2
Nó phụ thuộc vào cách sử dụng của nó. Nếu bạn đang liên kết đến một URL tải trang dựa trên các tham số đó, điều đó có ý nghĩa, nhưng nếu trang chính chỉ thực hiện cuộc gọi AJAX để lấy dữ liệu dựa trên các tham số bộ lọc, dù sao bạn cũng không thể đánh dấu trang đó ajax gọi và không có mang. Đương nhiên, bạn cũng có thể đánh dấu một URL mà khi bạn đến đó sẽ xây dựng một bộ lọc và POST nó cho cuộc gọi ajax và nó sẽ hoạt động tốt.
Daniel Lorenz

@DanielLorenz Để có trải nghiệm người dùng tốt nhất, URL vẫn phải được thay đổi thông qua API Lịch sử trong trường hợp đó. Tôi không thể đứng khi một trang web không cho phép sử dụng chức năng quay lại của trình duyệt để điều hướng đến các trang trước đó. Và nếu đó là một trang máy chủ tiêu chuẩn được tạo, cách duy nhất để làm cho nó có thể đánh dấu được là sử dụng yêu cầu GET. Có vẻ như các tham số truy vấn ol 'tốt là giải pháp tốt nhất.
Nathan

@Nathan Tôi nghĩ rằng tôi đã đọc sai câu trả lời này. Tôi đã nói về việc sử dụng các tham số chuỗi truy vấn trong một get. Bạn không bao giờ nên sử dụng các tham số cơ thể trong cuộc gọi GET vì điều đó sẽ hoàn toàn vô dụng. Tôi đã nói nhiều hơn về một GET với chuỗi truy vấn có thể được sử dụng / đánh dấu và sau đó khi khởi động trang, bạn có thể sử dụng các tham số đó để xây dựng bộ lọc để POST, sử dụng các tham số đó để lấy dữ liệu. Lịch sử vẫn sẽ hoạt động tốt trong kịch bản đó.
Daniel Lorenz

@DanielLorenz À không sao mà có ý nghĩa. Tôi nghĩ rằng tôi đã hiểu sai những gì bạn đang nói.
Nathan

70

Dường như việc lọc / tìm kiếm tài nguyên có thể được thực hiện theo cách RESTful. Ý tưởng là để giới thiệu một điểm cuối mới được gọi là /filters/hoặc /api/filters/.

Sử dụng bộ lọc điểm cuối này có thể được coi là một tài nguyên và do đó được tạo thông qua POSTphương thức. Bằng cách này - tất nhiên - cơ thể có thể được sử dụng để mang tất cả các tham số cũng như các cấu trúc tìm kiếm / bộ lọc phức tạp có thể được tạo.

Sau khi tạo bộ lọc như vậy, có hai khả năng để có kết quả tìm kiếm / bộ lọc.

  1. Một tài nguyên mới với ID duy nhất sẽ được trả về cùng với 201 Createdmã trạng thái. Sau đó, sử dụng ID này, một GETyêu cầu có thể được thực hiện /api/users/như sau:

    GET /api/users/?filterId=1234-abcd
    
  2. Sau khi bộ lọc mới được tạo thông qua POSTnó sẽ không trả lời 201 Creatednhưng cùng một lúc 303 SeeOthervới Locationtiêu đề trỏ đến /api/users/?filterId=1234-abcd. Chuyển hướng này sẽ được tự động xử lý thông qua thư viện cơ bản.

Trong cả hai trường hợp, hai yêu cầu cần phải được thực hiện để có kết quả được lọc - điều này có thể được coi là một nhược điểm, đặc biệt đối với các ứng dụng di động. Đối với các ứng dụng di động, tôi sẽ sử dụng một POSTcuộc gọi đến /api/users/filter/.

Làm thế nào để giữ các bộ lọc được tạo?

Chúng có thể được lưu trữ trong DB và được sử dụng sau này. Chúng cũng có thể được lưu trữ trong một số lưu trữ tạm thời, ví dụ như redis và có một số TTL sau đó chúng sẽ hết hạn và sẽ bị xóa.

Những lợi thế của ý tưởng này là gì?

Các bộ lọc, kết quả được lọc có thể lưu trong bộ nhớ cache và thậm chí có thể được đánh dấu.


2
Vâng, đây sẽ là câu trả lời được chấp nhận. Bạn không vi phạm các nguyên tắc REST và bạn có thể thực hiện các truy vấn phức tạp dài đối với tài nguyên. Nó đẹp, sạch sẽ và tương thích với bookmark. Hạn chế bổ sung duy nhất là nhu cầu lưu trữ các cặp khóa / giá trị cho các bộ lọc đã tạo và hai bước yêu cầu đã được đề cập.
dantebarba

2
Mối quan tâm duy nhất với phương pháp này là, nếu bạn có các bộ lọc thời gian trong truy vấn (hoặc giá trị thay đổi liên tục). Sau đó, số lượng bộ lọc để lưu trữ trong db (hoặc bộ đệm) là vô số.
Rvy Pandey

17

Tôi nghĩ bạn nên đi với các tham số yêu cầu nhưng chỉ khi không có tiêu đề HTTP phù hợp để thực hiện những gì bạn muốn làm. Đặc tả HTTP không nói rõ ràng rằng GET không thể có phần thân. Tuy nhiên , bài viết này nêu:

Theo quy ước, khi phương thức GET được sử dụng, tất cả thông tin cần thiết để xác định tài nguyên được mã hóa trong URI. Không có quy ước nào trong HTTP / 1.1 về tương tác an toàn (ví dụ: truy xuất) trong đó máy khách cung cấp dữ liệu cho máy chủ trong cơ thể thực thể HTTP thay vì trong phần truy vấn của URI. Điều này có nghĩa là đối với các hoạt động an toàn, URI có thể dài.


6
ElasticSearch cũng không NHẬN với cơ thể và hoạt động tốt!
Tarun Sapra

Vâng, nhưng họ kiểm soát việc thực hiện máy chủ có thể không phải là xase trên các interwebs.
dùng432024

7

Khi tôi đang sử dụng một phụ trợ laravel / php, tôi có xu hướng đi với một cái gì đó như thế này:

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

PHP tự động biến []params thành một mảng, vì vậy trong ví dụ này tôi sẽ kết thúc với một $filterbiến chứa một mảng / đối tượng của các bộ lọc, cùng với một trang và bất kỳ tài nguyên liên quan nào tôi muốn tải.

Nếu bạn sử dụng ngôn ngữ khác, đây vẫn có thể là một quy ước tốt và bạn có thể tạo một trình phân tích cú pháp để chuyển đổi []thành một mảng.


Cách tiếp cận này có vẻ hay, nhưng có thể có vấn đề với việc sử dụng dấu ngoặc vuông trong URL, hãy xem những gì nhân vật có thể sử dụng trong một url
Sky

2
@Sky Điều này có thể tránh được bằng cách mã hóa URI []. Sử dụng các biểu diễn được mã hóa của các ký tự này để tham số nhóm truy vấn là một thực tiễn nổi tiếng. Nó thậm chí còn được sử dụng trong JSON: API đặc tả .
jelhan

6

Đừng băn khoăn quá nhiều nếu API ban đầu của bạn có đầy đủ RESTful hay không (đặc biệt khi bạn chỉ ở giai đoạn alpha). Lấy hệ thống ống nước back-end để làm việc đầu tiên. Bạn luôn có thể thực hiện một số loại chuyển đổi / viết lại URL để ánh xạ mọi thứ, tinh chỉnh lặp đi lặp lại cho đến khi bạn có được thứ gì đó đủ ổn định để thử nghiệm rộng rãi ("beta").

Bạn có thể xác định các URI có tham số được mã hóa theo vị trí và quy ước trên chính các URI, được tiền tố bởi một đường dẫn mà bạn biết bạn sẽ luôn ánh xạ tới thứ gì đó. Tôi không biết PHP, nhưng tôi sẽ cho rằng một cơ sở như vậy tồn tại (vì nó tồn tại trong các ngôn ngữ khác có khung web):

.I E. Thực hiện loại tìm kiếm "người dùng" với param [i] = value [i] cho i = 1..4 trên cửa hàng số 1 (với value1, value2, value3, ... làm tốc ký cho các tham số truy vấn URI):

1) GET /store1/search/user/value1,value2,value3,value4

hoặc là

2) GET /store1/search/user,value1,value2,value3,value4

hoặc như sau (mặc dù tôi sẽ không đề xuất nó, nhiều hơn về điều đó sau)

3) GET /search/store1,user,value1,value2,value3,value4

Với tùy chọn 1, bạn ánh xạ tất cả các URI có tiền tố /store1/search/uservào trình xử lý tìm kiếm (hoặc bất kỳ ký hiệu PHP nào) để thực hiện tìm kiếm các tài nguyên trong store1 (tương đương với /search?location=store1&type=user.

Theo quy ước được tài liệu hóa và thi hành bởi API, các giá trị tham số 1 đến 4 được phân tách bằng dấu phẩy và được trình bày theo thứ tự đó.

Tùy chọn 2 thêm loại tìm kiếm (trong trường hợp này user) làm tham số vị trí # 1. Hoặc là lựa chọn chỉ là một sự lựa chọn mỹ phẩm.

Tùy chọn 3 cũng có thể, nhưng tôi không nghĩ rằng tôi sẽ thích nó. Tôi nghĩ rằng khả năng tìm kiếm trong một số tài nguyên nhất định phải được trình bày trong chính URI trước chính tìm kiếm đó (như thể chỉ ra rõ ràng trong URI rằng tìm kiếm là cụ thể trong tài nguyên.)

Ưu điểm của việc vượt qua các tham số trên URI là tìm kiếm là một phần của URI (do đó coi tìm kiếm là tài nguyên, tài nguyên có nội dung có thể - và sẽ - thay đổi theo thời gian.) Nhược điểm là thứ tự tham số là bắt buộc .

Khi bạn làm một cái gì đó như thế này, bạn có thể sử dụng GET và đó sẽ là tài nguyên chỉ đọc (vì bạn không thể POST hoặc PUT cho nó - nó sẽ được cập nhật khi nó được GET). Nó cũng sẽ là một tài nguyên chỉ tồn tại khi nó được gọi.

Người ta cũng có thể thêm nhiều ngữ nghĩa cho nó bằng cách lưu trữ các kết quả trong một khoảng thời gian hoặc với XÓA khiến bộ đệm bị xóa. Tuy nhiên, điều này có thể chạy ngược lại với những gì mọi người thường sử dụng XÓA cho (và vì mọi người thường kiểm soát bộ đệm ẩn với các tiêu đề bộ đệm.)

Làm thế nào bạn đi về nó sẽ là một quyết định thiết kế, nhưng đây sẽ là cách tôi đi. Nó không hoàn hảo và tôi chắc chắn sẽ có trường hợp thực hiện điều này không phải là điều tốt nhất để làm (đặc biệt đối với các tiêu chí tìm kiếm rất phức tạp).


7
Yo, nếu bạn (ai đó, bất cứ ai / bất cứ điều gì) những điều phù hợp để hạ thấp câu trả lời của tôi, nó sẽ làm tổn thương bản ngã của bạn ít nhất là đưa ra một nhận xét cho biết chính xác những gì bạn không đồng ý? Tôi biết đó là interweebz, nhưng ...;)
luis.espinal

107
Tôi đã không đánh giá thấp, nhưng thực tế là câu hỏi bắt đầu bằng: "Tôi hiện đang thiết kế và triển khai API RESTful" và câu trả lời của bạn bắt đầu bằng "Đừng băn khoăn quá nhiều nếu API ban đầu của bạn hoàn toàn RESTful hay không" sai với tôi Nếu bạn đang thiết kế API, bạn đang thiết kế API. Câu hỏi đặt ra là làm thế nào để thiết kế API tốt nhất, chứ không phải về việc liệu API có nên được thiết kế hay không.
vườn

14
API hệ thống, hoạt động trên API trước tiên, không phải hệ thống ống nước phụ trợ, việc triển khai đầu tiên có thể / chỉ nên là một bản giả. HTTP có một cơ chế để truyền tham số, bạn đang đề xuất nó được phát minh lại, nhưng tệ hơn (tham số được đặt hàng thay vì cặp giá trị tên). Do đó bỏ phiếu xuống.
Steven Herod

14
@gardarh - vâng, nó cảm thấy sai, nhưng đôi khi, nó thực dụng. Mục tiêu chính là thiết kế một API hoạt động cho bối cảnh kinh doanh. Nếu một cách tiếp cận RESTFULL đầy đủ phù hợp với doanh nghiệp trong tay, thì hãy thực hiện nó. Nếu không, thì đừng đi. Đó là, thiết kế một API đáp ứng các yêu cầu kinh doanh cụ thể của bạn. Đi khắp nơi cố gắng làm cho nó trở nên RESTfull vì yêu cầu chính của nó không khác nhiều so với việc hỏi "làm thế nào để tôi sử dụng mẫu bộ điều hợp trong vấn đề X / Y". Đừng mô hình sừng giày trừ khi chúng giải quyết các vấn đề thực tế, có giá trị.
luis.espinal

1
Tôi xem một tài nguyên như một bộ sưu tập trạng thái và các tham số như một phương tiện để thao túng biểu diễn của trạng thái đó theo tham số. Hãy nghĩ về nó theo cách này, nếu bạn có thể sử dụng các nút và công tắc để điều chỉnh cách hiển thị tài nguyên (hiển thị / ẩn các bit nhất định của nó, sắp xếp theo thứ tự khác, v.v ...) thì các điều khiển đó là params. Nếu đó thực sự là một tài nguyên khác (ví dụ '/ album' so với '/ nghệ sĩ'), thì đó là khi nó nên được thể hiện trong đường dẫn. Dù sao đó cũng là thứ trực quan với tôi.
Eric Elliott

2

FYI: Tôi biết điều này hơi muộn nhưng đối với bất kỳ ai quan tâm. Phụ thuộc vào mức độ RESTful mà bạn muốn trở thành, bạn sẽ phải thực hiện các chiến lược lọc của riêng mình vì thông số HTTP không rõ ràng về điều này. Tôi muốn đề xuất mã hóa url tất cả các tham số bộ lọc, vd

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

Tôi biết điều đó thật xấu xí nhưng tôi nghĩ đó là cách RESTful nhất để làm điều đó và nên dễ dàng phân tích về phía máy chủ :)

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.