Recent site activity

Services Policy Manual

Introduction

This is Visfleet's Services Policy Manual. Developers at Visfleet follow this guide in an attempt to keep our web services layer consistent and useful to both internal development, and customers consuming our APIs.

Heuristics

This policy manual is a guide to best practices, try to fit within the policy for web services described herein but be pragmatic with new challenges.

Restful Rails

Although the Rail's implementation of Restful continues to change, we still say that we use the conventional Restful Rails for our web services. Any deviation that's required is documented below. We break the deviations into two parts: 
  1. Additional Protocol - Ways of handling data better than the de-facto, without breaking the de-facto.
  2. Exceptional Protocol - Ways of handling data that causes the de-facto (rails rest) implementation to be broken.
The additional protocol should be our Standard. That is, we should implement this protocol wherever we can. It's very useful stuff.
The exceptional protocol deviations are troublesome but occasionally necessary. The reasoning for them is outlined, and we try to compartmentalise them in our services by relying on special Mime-Types or extensions such as the .flex extension.

Additional Protocol

Representation of entities

Identity

All entities must carry an identity consisting of one or more field. The identity must be documented. In most cases, the field 'id' is more than adequate.
For read only lists, any unique identity will suffice. For writable entities, the identity must be the same as the one used for update, delete, and per-entity verbs, e.g.:

For the path: /myApp/widgets/:id
:id must match the identity provided in the read operations. Again, this does not have to be a single field called id, but in almost all cases it will be.

Standard Fare

In all cases the following fields (if not null) should be represented:
  • created_at
  • updated_at
  • deleted_at (used to identify the record as deleted)
Note on deleted_at: Because we use soft deletion, it's possible to request deleted resources. 'deleted_at' is our way of knowing the resource is deleted.

Soft Deletion

Soft deletion involves keeping the record to be deleted in the database, but saving it with a delete_at matching the datetime it was deleted. Soft deletion should always modify the updated_at field as well.

Always Soft Delete

The only exception, is read only lists can use hard deletes, so long as it doesn't cause integrity issues.

Never damage data integrity 

Keep this in mind for Add's as well.
To avoid such a scenario, try:
  1. Implement the deletion as business logic that performs other necessary modifications to model to keep integrity - This is the DEFAULT behaviour of our framework
  2. Throw an error which sends the consumer to read the docs or with detail in the error. The detail or docs should advise the consumer what else they have to do to get a successful delete (such as delete a dependency first). This is the fallback position for developer when constructing appropriate business logic is untenable.
Deltas

We implement list deltas, which are changes to collections and items in the collection. It could be extended to individual elements (get /resourceCollection/:id) but as we don't use the 'show' action very often, it has not been implemented.

The aim of delta's is to support an effective 'synchronisation server' for clients that keep a local cache of records. 

Delta's hinge on the request parameters:

  • changed_since (default null) - a datetime which asks the server to return all records changed on or after the specified datetime. A value of null return all records (excepting other filters)
  • include_deleted (default false) - a boolean which asks the server (when true) to return soft-deleted records. 
And upon the response values:
  • last_modified (and Last-Modified) - a datetime which represents when the request began being handled by the server, to the second. It can also be the maximum datetime of all records updated_at but this is both less efficient, and runs the risk of a single invalid record (with an updated_at in the future) causing complete failure.
  • delta_mode (and X-DELTA-MODE) (if null assume 'delta') - a string with the value of either 'delta' or 'new'. "delta" means this is a delta and as such we can assume we only received changed records. "new" means this is a complete list and you must abandon your current list in favour of it.
Notes about response values:
Depending on the format of the response, these will need to be added in different places.
Typically they should both be included as response headers. As a response header use the spelling: "Last-Modified" as this will help with caching as well as deltas.

In the case of flex responses, we cant read the response headers, so we expect "last_modified" and "delta_mode" in the root node of the XML.

Delta Example

First List Request (get /resourceCollection)

First request to a list (get /resourceCollection) must send the parameter changed_since as null, or not send it at all. Typically we also leave out include_deleted. Sometimes it will be requested (include_deleted = true) to get administrative lists. For example, if you want to enable undeletes for a user.

The response is typical rest, with either headers for Last-Modified and X-DELTA-MODE or root node attributes last_modifed and delta_mode. If these don't come back, then the client will usually assume a delta, and because all records are included, will merge it with the local collection.

Further List Requests

Now when a request to the collection is made, the parameter changed_since is sent with the most recent Last-Modified (or last_modified) response from the server. It's possible to get some records twice in this manner because they could have been added/modified in the same second as the last response. This is preferable to the alternative where delta's may be missed.
This request will also set include_deleted to true, indicating that it needs to know about records deleted since the last modification.

Nested Updates

In nested representations, modifying the content of nested entities is achieved in place, by sending the entire representation (including the outer entity) back to the server. E.g.:

If for get /items/4 you recieve:
Item:
    id : 4
    name: floogle
    prices:
        price:
            id: 8
            quantity: 1+
            amount: 5.50
        price:
            id: 11
            quantity: 10+
            amount: 5.00
        price:
            id: 12
            quantity: 100+
            amount: 2.90

An update to the 1+ quantity would require a post /items/4 with:
Item:
    id : 4
    name: floogle
    prices:
        price:
            id: 8
            quantity: 1+
            amount: 5.90
        price:
            id: 11
            quantity: 10+
            amount: 5.00
        price:
            id: 12
            quantity: 100+
            amount: 2.90

The order of nested elements may have meaning, depending on the resource. For example, the order of prices may affect the display order, so you might send them back in reverse order to have the list display with larger quantity prices first.

Nested Additions

Similar to updates, additions are simply a modification of the nested list where you include an extra nested entity without an ID. You can add as many items as you like at once this way.

post /items/4
Item:
    id : 4
    name: floogle
    prices:
        price:
            id: 8
            quantity: 1+
            amount: 5.90
        price:
            id: 11
            quantity: 10+
            amount: 5.00
        price:
            quantity: 50+
            amount: 4.10
        price:
            id: 12
            quantity: 100+
            amount: 2.90

Remember, the order of entities can have meaning.

Nested Deletes

Deletion is also an update, however instead of returning the representation of a nested entity, you only return it's ID and the special field '_deleted' with a truth value.

post /items/4
Item:
    id : 4
    name: floogle
    prices:
        price:
            id: 8
            quantity: 1+
            amount: 5.90
        price:
            id: 11
            quantity: 10+
            amount: 5.00
        price:
            id: 45
            _deleted: true
        price:
            id: 12
            quantity: 100+
            amount: 2.90

Again, you can mix deletions with other updates including additions and re-ordering.

Paging

If paging is handled by the server, then you can specify the page by appending the following parameters to the query string.
  • page=[integer]. The page to return
  • per_page=[integer]. The number of entries per page
The following attributes are included in the root element of the returned resource.

<events current_page="2" per_page="100" total_pages="10" total_entries="998">
  ...
</events>
 
Exceptional Protocol

Handling Errors

For any browser plugin client technology (e.g. Flex/flash) access to http status codes is out of reach, and handling errors presented in another format (such as html pages) is patently silly. When the server issues a status code error, or a handled exception, the results for flex marked up xml look like:

<errors status_code="301" >
    <error>Content moved permanently</error>
</errors>

or 

<errors>
    <error>Resource must have a name</error>
    <error>Resource could not be saved</error>
</errors>

Note: status_code is a suggestion, we haven't settled on this yet.

Handling Status Codes

Non-error status codes need to be converted to a 200 response or flex will ignore the response (think it's an error)

Rule of thumb for .flex support is to always return a 200 code in the headers.

Underscores

All .flex serialisation should use underscores, not dashes or camelCase:

this_is_good

whereas

this-is-not
thisIsNotEither

* this does not apply to headers, only body content.

Addendum

Flex Limitations

Status Codes

We cannot support http status codes because flex wont see them. It will see a '0' or some of the '200' codes on ie. Best not to rely on them for anything

Request Headers

Flex supports any request header, so long as:

1. There is a body attached to the request
2. The header is not one of the following:

Accept-Charset
Accept-Encoding
Accept-Ranges
Age
Allow
Allowed
Authorization
Charge-To
Connect
Connection
Content-Length
Content-Location, 
Content-Range
Cookie
Date
Delete
ETag
Expect
Get
Head
Host
Keep-Alive
Last-Modified
Location
Max-Forwards,
Options
Post
Proxy-Authenticate
Proxy-Authorization
Proxy-Connection
Public
Put
Range
Referer
Request-Range
Retry-After
Server
TE
Trace
Trailer
Transfer-Encoding
Upgrade
URI
User-Agent
Vary
Via
Warning
WWW-Authenticate
x-flash-version.

Response headers

None are supported afaik. Try to embed them in the envelope of the body (e.g. root node of XML)

Request Method Header

Flex only supports Get and Post. Use the Rails request header: X_HTTP_METHOD_OVERRIDE