URI templates

One principle for the use of the REST API is that the clients should not hardcode/construct any URLs, but rather remember the URLs, and/or use URI templates to look them up.

The important point about using whole URLs and templates on the client side, is that the server is wholly responsible for constructing URLs, and this reduces the coupling between client and server and allows higher evolvability.

If using URI templates, the templates can and should be cached/stored at the client side, to avoid calling the Get templates API before every REST operation to find the URL to use.

Examples

{
   "templates": {
      "user-identity": "users/{userid}",
      "user-lookup-by-username": "users{?username}",
      "user-lookup-by-phone": "users?type=phone{&phone}",
      "user-accounts-collection": "users/{userid}/accounts",
      "user-accounts-identity": "users/{userid}/accounts/{accountid}",
      "user-mails-collection": "users/{userid}/mails",
      "user-mails-identity": "users/{userid}/mails/{mailid}",
      "user-rights-collection": "users/{userid}/rights",
      "user-rights-identity": "users/{userid}/rights/{rightid}",
      "user-subs-collection": "users/{userid}/subs",
      "user-subs-identity": "users/{userid}/subs/{subsid}"
   }
}

If e.g. the client has stored the Telenor Digital user ID and the subscription ID and wants to retrieve details about the subscription, the client needs to use the template with the key "user-subs-identity" together with the IDs to find the URL to GET.

If URLs change, the structure will also potentially change. We could for instance change path-variables to query-params, or remove the users/USERID part, to address a subscription directly based only on the subscription ID.

Some possible variants of the template user-subs-identity (hypothetically):

  • users/{userid}/subs/{subsid} - current
  • subs/{subsid}/users/{userid} - switched ordering of subscription ID and user ID
  • requestInfo{?subsid,userid} - query-params for subscription ID and user ID
  • requestInfo?type=subs{&subsid,userid} - a related variant of the one above
  • subs/{subsid} - only using the unique subscription ID
  • ...

The only safe way to store something other than the opaque complete URL, is to store the relevant template along with the variables. Then a future re-fetch of the template would yield a correct future URL when resolved against the variables.

Technical usage

The templates will be made available to GET in the REST API. A simple library for expanding URI templates can be found at https://github.com/Cloudname/uritemplate

Example code for using this library with the REST API would look like this: (Downloadable from https://github.com/vlarsen/sylfide-client-example)

package com.comoyo.sylfide.client;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;

import org.cloudname.uritemplate.UriTemplate;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

public class Client
{
    HttpHost targetHost = new HttpHost("localhost", 8080, "http");
    final private String apiEndPoint;

    public Client(String endpoint) {
        apiEndPoint = endpoint;
    }

    public void findURItemplates() throws IOException, JSONException {

        // Set up a connection to the API using Apache HTTP Client
        HttpClient httpClient = new DefaultHttpClient();
        HttpGet httpGet = new HttpGet(apiEndPoint);
        URI requestURI = httpGet.getURI();
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        String body = httpClient.execute(httpGet, responseHandler);

        // Pick up the templates using JSON parsing
        JSONObject templates = new JSONObject(body).getJSONObject("templates");

        // Somehow populate a map of variables
        Map<String, String> variables = new HashMap<String, String>();
        variables.put("userid", "123123123");
        variables.put("username", "foo@example.com");

        // Example showing expand of user identity template
        String userIdentityTemplate = templates.getString("user-identity");
        String userHrefExpanded = new UriTemplate(userIdentityTemplate).expand(variables);
        URI userHrefUri = requestURI.resolve(userHrefExpanded);
        System.out.println("userHrefUri = " + userHrefUri);

        // Example showing expand of user lookup template
        String userLookupTemplate = templates.getString("user-lookup-by-username");
        String userLookupExpanded = new UriTemplate(userLookupTemplate).expand(variables);
        URI userLookupUri = requestURI.resolve(userLookupExpanded);
        System.out.println("userLookupUri = " + userLookupUri);

    }
}

Resources

The URI template is specified in RFC 6570.