soulware.github.io


Associations and relationships can be represented in several ways in a JSON API. Simple one-to-one and one-to-many embedded associations were introduced in ID Style JSON API.

/api/users/1

{
  "user": {
    "id": 1,
    "name": "Foo Baz",
    "email_address" : {
      "id": 2,
      "email": "foo@example.com"
    }
  }
}

An alternative approach is to include an email_address_id attribute user document. A separate API request would be required to retrieve the associated email_address.

/api/users/1

{
  "user": {
    "id": 1,
    "name": "Foo Baz",
    "email_address_id" : 2
  }
}

/api/email_addresses/2

{
  "email_address" : {
    "id": 2,
      "email": "foo@example.com"
    }
  }
}

A similar approach can be used for one-to-many relationships, with an email_address_ids attribute -

/api/users/1

{
  "user": {
    "id": 1,
    "name": "Foo Baz",
    "email_address_ids" : [2, 3]
  }
}

The inefficient way to retrieve the email_addresses would be individually by id, with a separate API request for each -

/api/email_addresses/2

{
  "email_address" : {
    "id": 2,
      "email": "foo@example.com"
    }
  }
}

/api/email_addresses/3

{
  "email_address" : {
    "id": 3,
      "email": "foo@work.com"
    }
  }
}

The API could mitigate the performance problems here by including multiple documents in the API response -

/api/email_addresses?ids=2,3

{
  "email_addresses": [
    {
      "id": 2,
      "email": "foo@example.com"
    },
    {
      "id": 3,
      "email": "foo@work.com"
    },
  ]
}

Client and server would need to agree on the URL scheme to map between email_address_id 1 and /api/email_addresses/1. This would need to be hardcoded in the client. In simple cases like this the convention is relatively straightforward (email_address_id maps to an email_address, email_address_ids map to individual email_addresses).

An alternative to multiple API requests is side-loading (or compound documents). In this approach the associated documents would be returned along with the requested document. The overall JSON document would have multiple root elements -

/api/users/1

{
  "user": {
    "id": 1,
    "name": "Foo Baz",
    "email_address_ids" : [2, 3]
  },
  "email_addresses": [
    {
      "id": 2,
      "email": "foo@example.com"
    },
    {
      "id": 3,
      "email": "foo@work.com"
    },
  ]
}

Here the client would be able to map email_address_ids to the associated email_addresses within the same document.

The active_model_serializers gem implements both the embedded and side-loading approaches described here. A subsequent post will describe an alternative approach proposed for ember-data at jsonapi.org.

References:


16 June, 2013

Promises In Ember

Asynchronous loading of data in Ember has many benefits but it can introduce race-conditions if code is written with the assumption that data is available when it may still be loading.

Take a simple example like redirecting to a signin prompt unless the user is already authenticated. How do you safely do this when currentUser may still be loading asynchronously?

redirect: ->
  @transitionTo "signin" unless @get("currentUser")

If this is executed before currentUser has finished loading then the user will be immediately redirected, regardless of data returned.

The when/then promise syntax can be used if currentUser is loaded with ember-data or directly through a jQuery ajax call -

redirect: ->
  $.when(@get("currentUser")).then(
    (user) => @transitionTo "signin" unless user
  )

This is read as “_when_ the call to currentUser has successfully completed then run the specified block of code (in this case the conditional redirect). This is literally a way to wait for an asynchronous call defined elsewhere to complete before checking the result.

Ember handlebar templates are promise aware by default. The following code will render correctly (and re-render automatically) once currentUser has been loaded -

{{#if currentUser}}
  {{currentUser.name}}
{{else}}
  Guest
{{/if}}

Code in controllers or routes is not automatically promise aware; code is not automatically rerun once a model is updated.

We cannot for example, guarantee the currentUser will be loaded when we attempt to print it directly in the console -

user = @get("currentUser")
console.log(user.get("name"))

The when/then syntax provides a clean way to wait for the data to be available before accessing it -

 $.when(@get("currentUser")).then(
   (user) -> console.log user.get("name")
 )

The when/then syntax can also be safely used even if the values are not promises. The same syntax can be used to handle both synchronous and asynchronous data -

x = true
$.when(x).then(
  (result) -> console.log result
)

References -


12 June, 2013

ID Style JSON API

The simplest way of handling ids in a JSON API response is to simply treat the id as any other attribute.

GET /users/1

{
  "user": {
    "id": 1,
    "name": "Foo Baz"
  }
}

Embedded has_one or belongs_to associations can use the same approach for ids -

{
  "user": {
    "id": 1,
    "name": "Foo Baz",
    "email_address" : {
      "id": 2,
      "email": "foo@example.com"
    }
  }
}

The same approach also works when returning a collection -

GET /users

{
  "users": [
    { "id": 1, "name": "Foo Baz" },
    { "id": 2, "name": "Bar Baz" }
  ]
}

And embedded has_many associations -

{
  "user": {
    "id": 1,
    "name": "Foo Baz",
    "email_addresses" : [
      { "id": 2, "email": "foo@example.com" },
      { "id": 3, "email": "foo@work.com" }
    ]
  }
}

This basic approach to ids keeps things simple on the server but the client requires some prior knowledge of the URI scheme in order to navigate through various API calls. For example, what is the URI for email_address 2 in the example above? The URI may very likely be /email_addresses/2 or /users/1/email_addresses/2 but the client needs to know the appropriate URI scheme ahead of time.

References:


Content negotiation supports multiple API versions with a single set of API endpoints. It is the best way to provide backward compatibility without forcing clients to manage versioned endpoints.

The simple way is to version the URI itself -

http://example.com/api/v1/accounts/1

Each resource will be available on multiple different URIs as the API version number changes. Clients will be forced to handle multiple endpoints for a given resource. URIs stored by the client may need to be rewritten as the version number changes. Don’t do this.

Use the accept header to request a specific version of the API by including the version in the media type -

Accept: application/vnd.com.example.v1+json

An accept header application/json, text/html can be used to specify the generic media type. A vendor media type application/vnd.com.example+json can be used to request a document that conforms to an particular known format. This can be taken be taken a step further to include the version in the media type application/vnd.com.example.v1+json.

With this approach multiple API versions can be accessed with a single URI -

http://example.com/api/accounts/1

A client can request a particular version by specifying the appropriate media type in the accept header -

Accept: application/vnd.com.example.v1+json

References:


09 June, 2013

First Post

Boom. First post.