Skip to content

Resolving Ambiguity in Complex API Design

August 18, 2012

Twice I’ve encountered these issues, and twice I’ve cobbled together something that works. Still, I don’t feel like the solutions were elegant or developer-friendly.

Problem 1: Object Model Ambiguity

In systems with user models that involve multiple roles, especially when some users (producers) are curating collections of objects and others (consumers) are interacting with these objects, it can be challenging to define the relationships. If a producer is curating a set of playlists, one might assume that user.playlists would contain the playlist objects the producer owns. On the other end, the consumer might want to follow a set of playlists curated by others. From the consumer’s perspective, user.playlists might contain playlists the consumer is following.

Solution 1: Functional Relationships

In the example given above, the ambiguity lies in the naming of the relationships between User and Playlist. Instead of trying to combine the relationships into a conditional model, where the relationship means one thing in one situation and another thing in another situation, we simply separate the relationships into distinct associations. We further clarify the relationships by choosing names that more precisely describe the functional relationship between the objects. For playlists curated by a producer, we define user.curated_playlists. For playlists followed by a consumer, we define user.followed_playlists.

Problem 2: Single vs Multiple Requests

When interacting with an API, there are different ways to specify the focus of client requests. RESTful design patterns recommend representing the relationship of objects in their resource paths. Requesting /user may return only the user attributes, or it may return a set of nested collections or objects. Requesting /user/playlists returns playlists for the current user.

GET /user
{
 user: {
  first_name: '...', 
  last_name: '...', 
  email: '...',
  playlists: [{id: 1, name: '...'}, {id: 2, name: '...'}]
 }
}

or

GET /user
{
 user: {
  first_name: '...',
  last_name: '...',
  email: '...'
 }
}

GET /user/playlists
[
 {playlist: {id: 1, name: '...'}},
 {playlist: {id: 2, name: '...'}}
]

Solution 2: Offer Both and Let Client Decide

API design focuses on meeting the needs of many. Some clients will want to make a single request to sync content. Others will want to interact with specific collections in the object model hierarchy. By providing both mechanisms, the client can choose the endpoint(s) that best fit their specific needs.

Bonus Points

In a perfect world, the client can configure the response content at the interface level. Using the example given above, we might design the endpoint controller to respond to query string params to specify which fields to include.

GET /user?only=id,email&includes[playlists][only]=id,name&includes[playlists][methods]=follower_ids
{
 user: {
  id: 1,
  email: '...',
  playlists: [{id: 1, name: '...', follower_ids: [2,3,4]}, {id: 2, name: '...', follower_ids: [4]}]
 }
}

About these ads
No comments yet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 93 other followers

%d bloggers like this: