Building Calliope – Services Design

If you don’t know what Calliope is, please read this other post before continuing on.

In this post I’m going to describe how the web services forming the backend of Calliope have been designed and built. I didn’t concentrate much on designing them because I was eager to reach a visual result, so I know they’re not the best possible; I’m open to criticism and I hope with your help to improve the design.

Introduction

At the time I was beginning to develop Calliope I didn’t even know what RESTful was meaning, but there was quite a big hype around the word, so I decided to do some reading and figure out if that was the way to go. The book I chose for my research was RESTful Web Services by Richardson and Ruby (Kindle Edition of course).

After the reading it was clear that RESTful services where the right choice for my kind of application, mainly because of the promised lightness, which is well suited to reduce bandwidth occupation and, as a consequence, response time.
After a little thinking I also decided that a good format for the services would be JSON: JSON is well understood by JavaScript and PHP, is lightweight and expressive enough for almost anything. JSON has no built-in way to connect services together (like writing links in XHTML), but this shouldn’t be a problem for my application.
Last but not least, I decided to build my services as completely stateless in order to exploit all the advantages that statelessness brings with it.

To summarize, here are my choices:

  • be RESTful;
  • use JSON as the format;
  • be stateless.

The Services

RESTful services means thinking at your system as a bunch of resources, deciding one (or more) URI for each resource, and deciding how to represent it. With reference to the data model I described earlier, the main resources I can identify in Calliope are:

  • players;
  • games;
  • turns;
  • letters;
  • actions.

Every player can have a number of open games, every game is made of turns and actions. Actions can be linked either only to a game or to a game and specific turn; letters are linked to turns in order to implement the snapshot feature. As an additional information, not everyone can see every game: one can only see the games he or she is taking (or has taken) part in.
Every resource is identified in the data model by a numeric id; that id is the natural choice for addressing the resource through an URI but, as we will see later – is not always the best one.

Players

Let’s talk about players first. Players are the simplest resource of the group, and I decided to model the service as follows:

GET    /players                  retrieve list of players
POST   /players                  create a new player

GET    /players/{name}           retrieve info on player {name}
PUT    /players/{name}           edit info of player {name}
DELETE /players/{name}           delete player {name} 

GET    /players/{name}/avatar    retrieve avatar of player {name}
PUT    /players/{name}/avatar    edit avatar of player {name}
DELETE /players/{name}/avatar    delete avatar of player {name}

Explanation for this is quite obvious: by issuing a GET on /players one can retrieve the complete list of players registered to the game (right now there is no restriction, in the future the results could be restricted to only the “friends” of the current user, or such). Here is an example of the response in JSON format:

[
  {
    "name":"barfoo",
    "displayName":"Foo",
    "playerId":2
  },
  {
    "name":"foobar",
    "displayName":"Bar",
    "playerId":1
  }
]

Once you have the list you can then retrieve detailed information on a certain player (say its username is “foobar”) by building an URI with the second rule above (in the example: /players/foobar). The response will look like a single object of the list response, with additional information (maybe about the user’s profile, or game stats) added to it; at the current stage of development a request of a single player produces exactly the same object as the list request.
Anyway, by having the player username, you can also retrieve its avatar. Retrieving it is as simple as issuing a GET on /players/foobar/avatar. As a representation of the avatar an image is returned, it can be in one of the common formats of the internet, and could also be in a specific size.

In the previous paragraphs I only described the read part of services; there is also a write part, involving the other http methods:

  • issuing a POST on /players let you creating a new player (“posting” a new player);
  • issuing a PUT or a DELETE on /players/foobar (or on the avatar) let you respectively edit or delete the specified item.

In order to make one’s own stuff at hand, I decided to alias the resource /players/{my_username} with the simpler /me. Methods allowed, formats and semantics remain the same as stated above.

Games

Since there is no such thing as a “public” game, I decided to put all the games stuff under the /me resource, as such:

GET    /me/games                 retrieve list of my games
POST   /me/games                 create a new game
GET    /me/games/{id}            retrieve info on game {id} 
PUT    /me/games/{id}            edit info of game {id}

These services are similar to the one for players, except for the fact that the DELETE method is not supported, and that the numeric id is used for games whilst the username was used for players. I decided not to allow the deletion of games but only the hiding/archiving of them (action, this, that can be performed through a PUT on the game).

The representation of the game is quite simple, here is an example:

{
  "time":"2011-03-11 21:56:44",
  "ended":false,
  "gameId":10,
  "turnCount":19,
  "players": [ 1, 2 ]
}

As you can see there is the game creation time, a flag stating whether the game has ended or not, the id of the game (which is meaningful only when retrieving a list of games), the number of turns (which come in handy later) and an ordered array of player ids taking part into the game.

Turns and Letters

Turns and letters are closely linked, and since generally there is no reason one would need a single letter I decided to threat them together, so that letters are retrieved along when you ask for a specified turn. Moreover, I thought that the best way to address a turn inside a game is by its natural order number (first turn is turn 1, secondi is turn 2 and so on). This makes for a natural, compact and simple notation. Here are the routes (assume that * is replaced by /me/games/{id}):

GET    */turns            retrieve list of turns of the game
POST   */turns            create a new turn
GET    */turns/{#}        retrieve info and letters for turn {#}

A turn is viewed as a static snapshot of the game state and as such, once created, is immutable. For such reason neither PUT nor DELETE methods are allowed on it.
When retrieving the list of turns only a brief information is returned about each turn, for example:

{
  "time":"2011-03-11 21:56:44",
  "ended":false,
  "player":1,
  "turnNumber":1
}

This doesn’t add much to the information given by retrieving the game itself, anyway this method is present for coherence with the others. What is instead interesting is that by retrieving a single turn one also receives the complete list of the letters forming the game snapshot at the beginning of the retrieved turn. Letters are retrieved in a “compressed” form, in order to occupy the less space possible, here is an example:

{
  "time":"2011-03-11 21:56:44",
  "ended":false,
  "player":1,
  "turnNumber":1,
  "letters": [
    { "id":10369, "lid":1, "l":"A", "c":"s" },
    { "id":10370, "lid":2, "l":"A", "c":"s" },
    ...
  ]
}

Where:

  • id is the letter data model identifier;
  • lid is the letter id in the current game;
  • l is the letter string;
  • c is the letter container (b for board, r for rack, s for sack).

In order to create a new turn issuing a POST on */turns one has to provide the full snapshot of the game in the form of letters and their position, in the same format as the one described above. You can of course start a new turn only if you’re playing in the current one.

Actions

As I said above, actions can be linked to a game alone (and is the case of chat messages), or to a game and a specified turn. In order to account for the two possibilities I defined these 4 routes (as before, assume that * is replaced by /me/games/{id}):

GET    */actions            retrieve list of actions of the game
POST   */actions            add a new action to the game
GET    */turns/{#}/actions  retrieve list of actions of the turn {#}
POST   */turns/{#}/actions  add a new action to the turn {#}

So, POSTing to */actions means creating a “global” game action, as opposed to POSTing to */turns/{#}/actions, which means posting a new action relative to the specified turn only.

By common sense and coherence with the other routes, GETting */actions should return only global actions, whilst GETting */turns/{#}/actions should return only local actions. But this choice is not ideal when seeing the game as an ordered stream of actions (this kind of view was introduced in the data model article, and is essential to the reconstruction of game state through actions). In order to effectively support this kind of view of the game I decided to make local turn actions also available via the “global” */actions. In such a way, when one issues a GET on */actions, the response will contain not only the global actions of the game, but also the local actions linked to a turn.
Moreover, since the number of actions can become quite large, there is a need for filtering and selection of the results. As of now, I defined these additional parameters for */actions:

  • afterAction={actionId} – tells the service to return only actions with an id larger that the one specified in {actionId}, this is useful during online actions re-execution, in order to receive only the new actions (remember that the services are stateless, and so there is no way for the server to know which actions are already been received by the client);
  • pivotOnly – tells the service to retrieve only pivotal actions, in order to speedup the re-execution of a large set of actions;
  • turn={turnNumber} – tells the service to retrieve only those actions belonging to the specified turn.

A typical call sequence for this service – which is the core of the whole application – looks like:

  1. GET */actions?turn={lastTurn}&pivotOnly
  2. GET */actions?afterAction={lastSeenAction1}
  3. GET */actions?afterAction={lastSeenAction2}

Where {lastTurn} is the number of the last turn of the game, as received getting information on the game itself; {lastSeenAction1} and {lastSeenAction2} are the ids of the last action received by the client respectively after call 1 and after call 2.

The representation of a single action looks like this:

{
  "type":"letter_drag_end",
  "attributes":"{ ... }",
  "time":"2011-04-08 09:05:00",
  "t":"1302246300040",
  "actionId":24456,
  "pivot":true,
  "player":1
}

As you can see this provides for the actionId, that can be used to set the value of the parameter afterAction.

Reference

Here is the complete list of the designed services. The ones in italic are yet to be implemented, whilst the others are more or less stable.

GET /players                  retrieve list of players
POST /players create a new player

GET /players/{name}           retrieve info on player {name}
PUT /players/{name} edit info of player {name}
DELETE /players/{name} delete player {name} 
GET /players/{name}/avatar    retrieve avatar of player {name}
PUT /players/{name}/avatar edit avatar of player {name} 
DELETE /players/{name}/avatar delete avatar of player {name}

GET /me/games                 retrieve list of my games
POST /me/games                create a new game
GET /me/games/{id}            retrieve info on game {id} 
PUT /me/games/{id} edit info of game {id}

and with * = /me/games/{id}:

GET */turns               retrieve list of turns of the game
POST */turns              create a new turn
GET */turns/{#}           retrieve info and letters for turn {#}

GET */actions             retrieve list of actions of the game
POST */actions            add a new action to the game
GET */turns/{#}/actions   retrieve list of actions of the turn {#}
POST */turns/{#}/actions  add a new action to the turn {#}

Last Note

If you read this far please take the time to comment on this one, to let me know what you liked of the design, what you didn’t, and what you would do differently. Thank you.

« Go back to the index

Advertisements

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 )

Google+ photo

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

Connecting to %s