Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
465 views
in Technique[技术] by (71.8m points)

rest - Different RESTful representations of the same resource

My application has a resource at /foo. Normally, it is represented by an HTTP response payload like this:

{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}

The client doesn't always need all four members of this object. What is the RESTfully semantic way for the client to tell the server what it needs in the representation? e.g. if it wants:

{"a": "some text", "b": "some text", "d": "some text"}

How should it GET it? Some possibilities (I'm looking for correction if I misunderstand REST):

  • GET /foo?sections=a,b,d.
    • The query string (called a query string after all) seems to mean "find resources matching this condition and tell me about them", not "represent this resource to me according to this customization".
  • GET /foo/a+b+d My favorite if REST semantics doesn't cover this issue, because of its simplicity.
    • Breaks URI opacity, violating HATEOAS.
    • Seems to break the distinction between resource (the sole meaning of a URI is to identify one resource) and representation. But that's debatable because it's consistent with /widgets representing a presentable list of /widget/<id> resources, which I've never had a problem with.
  • Loosen my constraints, respond to GET /foo/a, etc, and have the client make a request per component of /foo it wants.
    • Multiplies overhead, which can become a nightmare if /foo has hundreds of components and the client needs 100 of those.
    • If I want to support an HTML representation of /foo, I have to use Ajax, which is problematic if I just want a single HTML page that can be crawled, rendered by minimalist browsers, etc.
    • To maintain HATEOAS, it also requires links to those "sub-resources" to exist within other representations, probably in /foo: {"a": {"url": "/foo/a", "content": "some text"}, ...}
  • GET /foo, Content-Type: application/json and {"sections": ["a","b","d"]} in the request body.
    • Unbookmarkable and uncacheable.
    • HTTP does not define body semantics for GET. It's legal HTTP but how can I guarantee some user's proxy doesn't strip the body from a GET request?
    • My REST client won't let me put a body on a GET request so I can't use that for testing.
  • A custom HTTP header: Sections-Needed: a,b,d
    • I'd rather avoid custom headers if possible.
    • Unbookmarkable and uncacheable.
  • POST /foo/requests, Content-Type: application/json and {"sections": ["a","b","d"]} in the request body. Receive a 201 with Location: /foo/requests/1. Then GET /foo/requests/1 to receive the desired representation of /foo
    • Clunky; requires back-and-forth and some weird-looking code.
    • Unbookmarkable and uncacheable since /foo/requests/1 is just an alias that would only be used once and only kept until it is requested.
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I would suggest the querystring solution (your first). Your arguments against the other alternatives are good arguments (and ones that I've run into in practise when trying to solve the same problem). In particular, the "loosen the constraints/respond to foo/a" solution can work in limited cases, but introduces a lot of complexity into an API from both implementation and consumption and hasn't, in my experience, been worth the effort.

I'll weakly counter your "seems to mean" argument with a common example: consider the resource that is a large list of objects (GET /Customers). It's perfectly reasonable to page these objects, and it's commonplace to use the querystring to do that: GET /Customers?offset=100&take=50 as an example. In this case, the querystring isn't filtering on any property of the listed object, it's providing parameters for a sub-view of the object.

More concretely, I'd say that you can maintain consistency and HATEOAS through these criteria for use of the querystring:

  • the object returned should be the same entity as that returned from the Url without the querystring.
  • the Uri without the querystring should return the complete object - a superset of any view available with a querystring at the same Uri. So, if you cache the result of the undecorated Uri, you know you have the full entity.
  • the result returned for a given querystring should be deterministic, so that Uris with querystrings are easily cacheable

However, what to return for these Uris can sometimes pose more complex questions:

  • returning a different entity type for Uris differing only by querystring could be undesirable (/foo is an entity but foo/a is a string); the alternative is to return a partially-populated entity
  • if you do use different entity types for sub-queries then, if your /foo doesn't have an a, a 404 status is misleading (/foo does exist!), but an empty response may be equally confusing
  • returning a partially-populated entity may be undesirable, but returning part of an entity may not be possible, or may be more confusing
  • returning a partially populated entity may not be possible if you have a strong schema (if a is mandatory but the client requests only b, you are forced to return either a junk value for a, or an invalid object)

In the past, I have tried to resolve this by defining specific named "views" of required entities, and allowing a querystring like ?view=summary or ?view=totalsOnly - limiting the number of permutations. This also allows for definition of a subset of the entity that "makes sense" to the consumer of the service, and can be documented.

Ultimately, I think that this comes down to an issue of consistency more than anything: you can meet HATEOAS guidance using the querystring relatively easily, but the choices you make need to be consistent across your API and, I'd say, well documented.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...