There are some general guideline and hints about how to define it, but no explicit standard or accepted schema structure to use.
After reading some info on the web, I think I manage to crack the pattern :-)
I would like to share the rules and structure I formed and hopefully to get some feedback about it and improve it, so please don't hesitate to leave notes and point any pain point that structure has.
The high level pattern is:
http(s)://server.com/app-name/{version}/{domain}/{rest-convention}
Where {version} is the api version this interface work with and {domain} is an area you wish to define for any technical (e.g. security - allow certain users to access that domain) or business reason (e.g. gather functionality under same prefix).
The {rest-convention} denotes the set of REST API which is available under that domain.
It has the following convention:
- singular-resourceX/
- URL example: order/ (order is the singular resource X)
- GET - will return a new order
- POST - will create a new order. values are taken from the post content body.
- singular-resourceX/{id}
- URL example: order/1 (order is the singular resource X)
- GET - will return an order with the id 1
- DELETE - will delete an order with the id 1
- PUT - will update an order with the id 1. Order values to update are taken from the post content body.
- plural-resourceX/
- URL example: orders/
- GET - will return all orders
- plural-resourceX/search
- URL example: orders/search?name=123
- GET - will return all orders that answer search criteria (QBE, no join) -order name equal to 123
- plural-resourceX/searchByXXX
- URL example: orders/searchByItems?name=ipad
- GET - will return all orders that answer the customized query - get all orders that associated to items with name ipad
- singular-resourceX/{id}/pluralY
- URL example: order/1/items/ (order is the singular resource X, items is the plural resource Y)
- GET - will return all items that associated to order #1
- singular-resourceX/{id}/singular-resourceY/
- URL example: order/1/item/
- GET - return a new item (transient) that is associated order #1
- POST - create a new item and associate it to order #1. Item values are taken from the post content body.
- singular-resourceX/{id}/singular-resourceY/{id}/singular-resourceZ/
One basically can have further nesting as long as the above convention is maintained and no plural resource is defined after another plural resource.
There are further guidelines/notes to make things clear:
- When using plural resource, the returning instances will be those of the last plural resource used.
- When using singular resource the returning instance will be the last singular resource used.
- On search, the returning instances will be those of the last plural entity used.
Hopefully your insight will help me improve this structure and overcome issues which you might came across.
In next post, after this suggested structure will be improved, I will try to give technical examples how to implement it using Spring MVC 3.1
You should have something like
ReplyDeletehttp(s)://server.com/app-name/{version}/{domain}/{rest-convention}
where {version} is the version of the protocol. Suppose you have a new field on order, if you update the protocol, you should not break backward compatibility of older clients
I like the version suggestion very much.
DeleteI'll update the structure.
You can always user the Request Headers for the Version.
ReplyDeleteUsing the Header, might harm the principle of easy discovery.
DeleteMoreover it might be not so trivial for non technical users and might introduce complexity to this simple protocol.
Like it ...
ReplyDeleteOnly one question that I'd like to hear your opinion on. And it might come up when you talk about the tech side with spring.
How do you handle updates? or state changes i.e.
Say I have an order, I can do a PUT to update the order, and before its processed I can change whatever I want. But once its been shipped, the set of things that a PUT request can do has changed. Do you have any patterns for handling the available actions based on the state of an object?
Hello, I like all of this except for the /search and /searchByItems URI components. The "?" delimiter should be sufficient to indicate a search by parameter-value pairs. Using "/search" puts a verb where a noun belongs: the only verbs in REST are the HTTP methods GET / POST / PUT / DELETE, etc.
ReplyDeleteTo search for a resource by one of its sub-resources, could the parameter name be specified in this way?
orders?item.name=iPad
That was my one issue as well.. is "search" a resource? It's simpler to treat any search as a parameterised GET of the resource collection you are after (think of it as a filter)
DeleteDoes it mean order/ GET will create and return a new (default) order?
ReplyDeleteIt will not create it in the DB. It will return a new instance w/o an id.
DeleteThis comment has been removed by the author.
ReplyDeleteCorrect.
DeleteRespectfully, your URL structure is all wrong. URLs are supposed to describe resources, the THINGS your application is interested in. Nouns, not verbs.
ReplyDeletehttp://server.com/app-name/{version}/{domain}/{rest-convention}
app-name is not a resource. I might compromise here to avoid naming conflicts.
version is not a resource. Versioning is better accomplished by versioning MIME types. Just do a Web search on REST versioning and you'll see plenty of discussion about this.
domain is not a resource.
orders/search?name=123
search is not a resource.
Better is /orders?name=123
orders/searchByItems?name=ipad
More difficult. I would probably use
/orders?itemName=123. If your app has complex search requirements, perhaps
orders?criteria="A complex search string"
Hi Douglass,
DeleteThanks for the comment. Been waiting for someone with some concerns about the suggested pattern.
Regarding your comment - first - I think that pure REST is not a silver bullet and complex business app need to have some adjustment, while sticking as much as possible to the REST philosophy.
* version- URL vs. Header: I took google approach and put version in the URL. IMHO it is more discoverable for humans and easier to use as API for developers.
* domain is for technical reasons (such as security simplification) and provide additional technical flexibility. Till now, the URL has nothing to do with REST. REST standard starting after the domain section.
* Complex queries - that's where the fun stuff starts and the REST deviation would probably take place: Assuming each resource has multiple complex queries, some with overlapping parameters, in your approach the server would have impossible task to know which one of the queries the client refer to. Therefore you have to explicitly differentiate it.
Let me know if you have other concerns. Thanks!
Way late comment...
ReplyDelete*API* versions do not belong in *resource* identifiers. Share resources across API versions. Use media types for data formats. If you google RESTful Versioning, you'll find lots of explanation for why URI based versioning is a rookie mistake.
Usually, people put individual resources under a plural identifier, as if it is a folder in the file system. So /orders/1 is a specific order and /orders?name={foo} is a collection of hits. I can create an order via a POST to /orders. That said, there is nothing wrong or unRESTful with doing it your way.
I partially agree with Douglass above that /orders?name=123 is a succinct and clear syntax. But there is nothing wrong with including application IF you will never share resources across applications. But it's more idiomatic to use the hostname for this.
I disagree with him that putting domain in for a security or business reason is wrong. The path part of a URI is for hierarchical organization of resources. If you have business unit's bu-A and bu-B, it is completely reasonable to have /bu-A/order/1 and /bu-B/order/2. REST does not require you to have a single path for the same "TYPE" of resource and actually discourages clients from expecting all resources to have matching paths.
I tend to agree that adding "search" in the hierarchy is needless. But it isn't WRONG, provided you are actually following HATEOAS and building up these URIs for your clients instead of having them construct an RPC call by binding in the arguments they way.