
Loading ....
Building Restful services (Notes)Definitions: | |
An idempotent operation produces the same result, whether it is invoked 1 or multiple times. | |
PUT, GET, DELETE and HEAD are idempotent operations | |
****************************** | |
* GET as Read * | |
****************************** | |
****************************** | |
* DELETE as Delete * | |
****************************** | |
****************************** | |
* HEAD as Headers, no body * | |
****************************** | |
****************************** | |
* PUT as Create * | |
****************************** | |
- Identifier is known upfront by client. | |
- The client must provide all properties/data of that resource | |
PUT /users/{robbypelssers} | |
{ | |
"firstname": "Robby", | |
"lastname": "Pelssers", | |
"age": 36, | |
"phone": "phonenumberOld" | |
} | |
****************************** | |
* POST as Create * | |
****************************** | |
- On a parent resource (typicaly a collection) | |
POST /users | |
{ | |
"firstname": "Robby", | |
"lastname": "Pelssers", | |
"age": 36, | |
"phone": "phonenumberOld" | |
} | |
Response: 201 created | |
Location: http://www.mycompany.com/users/{robbypelssers} | |
****************************** | |
* POST as Update * | |
****************************** | |
- partial or full update | |
- saves bandwidth | |
POST /users/{robbypelssers} | |
{ | |
"phone": "phonenumberNew" | |
} | |
Response: 200 OK | |
****************************** | |
* Media Types * | |
****************************** | |
- Format specification + parsing rules | |
- Request: Accept header | |
- Response: Content-Type header | |
application/json | |
****************************** | |
* Designing Rest Services * | |
****************************** | |
* Pick an easy to remember base URL, e.g. http(s)://api.mycompany.nl | |
* Versioning: | |
http(s)://api.mycompany.nl/v1 | |
or | |
Media-Type | |
application/json;application,v=1 | |
* Resource Formats: | |
Date / Time / Timestamps: use ISO 8601 | |
* Content Negotiation (2 ways of negotiation) | |
[1] Header | |
- Acccept Header | |
- Header values comma delimited in order of preference | |
GET /user/robbypelssers | |
Accept: application/json, text-plain | |
[2] Resource extension (conventionally overrides Accept header) | |
/users/robbypelssers.json | |
/users/robbypelssers.xml | |
/users/robbypelssers.csv | |
* Resource referencing (aka linking) | |
- Hypermedia is paramount | |
- Linking is fundamental to scalability | |
- Tricky in JSON | |
- XML has it (XLink), JSON doesn't | |
Instance reference: | |
GET users/robbypelssers | |
200 OK | |
{ | |
"href": "http://api.mycompany.nl/users/robbypelssers", | |
"firstname": "Robby", | |
"lastname": "Pelssers", | |
"age": 36, | |
"phone": "phonenumberNew", | |
"children": [ | |
{"href": "http://api.mycompany.nl/users/lindseypelssers"}, | |
{"href": "http://api.mycompany.nl/users/valeriepelssers"} | |
] | |
} | |
* Reference expansion (aka Entity expansion, Link expansion) | |
- reduce number of requests by allowing clients to specify expansion of specific properties | |
GET users/robbypelssers?expand=children | |
{ | |
"href": "http://api.mycompany.nl/users/robbypelssers", | |
"firstname": "Robby", | |
"lastname": "Pelssers", | |
"age": 36, | |
"phone": "phonenumberNew", | |
"children": [ | |
{ | |
"href": "http://api.mycompany.nl/users/lindseypelssers", | |
"firstname": "Lindsey", | |
"lastname": "Pelssers", | |
"age": 10 | |
}, | |
{ | |
"href": "http://api.mycompany.nl/users/valeriepelssers", | |
"firstname": "Valerie", | |
"lastname": "Pelssers", | |
"age": 6 | |
} | |
] | |
} | |
GET users/robbypelssers?expand=* --> expand everything 1 level deep | |
* Partial representation | |
GET users/robbypelssers?fields=firstname, lastname | |
* Pagination | |
Collection resource supports query params offset and limit | |
GET /users?offset=50&limit=25 | |
GET /users | |
Response: 200 OK | |
{ | |
"href": "http://api.mycompany.nl/users" | |
"limit": 25 | |
"offset": 0, | |
"first": {"href": "http://api.mycompany.nl/users?offset=0"}, | |
"previous": null, | |
"next": {"href": "http://api.mycompany.nl/users?offset=25"}, | |
"last": {"href": "http://api.mycompany.nl/users?offset=145"}, | |
"items": [ | |
{...}, | |
{...} | |
] | |
} | |
* Errors | |
- As descriptive as possible | |
- As much information as possible | |
- Developers are your customers | |
POST /users | |
Response: 409 conflict | |
{ | |
"status": 409, | |
"code": 40912, | |
"exception": "UserAlreadyExistsException", | |
"message: "A user 'robbypelssers' already exists", | |
"href": "http://api.mycompany.nl/docs/api/errors/40912" | |
} | |
* IDs | |
- should be opaque | |
- Should be globally unique | |
- Avoid sequential numbers (contention) | |
- Good candidates (UUID, Url64) | |
* HTTP Method overrides | |
POST /user/robbypelssers?_method=DELETE | |
* Caching and concurrency control | |
- Server (initial response) | |
Etag: "678988546345a76b" | |
- Client (later request) | |
If-None-Match: "678988546345a76b" | |
- Server (later response) | |
304: Not Modified | |
* Security | |
- Avoid sessions when possible | |
- Authenticate every request if necessary | |
- Stateless | |
- Authorize based upon resource content, not URL | |
- Use existing protocols: OAuth, Basic over SSL only | |
- Use custom scheme | |
- only if you provide client code / SDK | |
- only if you really know what you're doing | |
* Maintenance | |
- Use HTTP Redirects | |
- Create abstraction layer/ endpoints when migrating | |
- Use well defined custom Media Types |