Designing and implementing flexible RESTful services

Dominic Burford
Zero Equals False
7 min readJun 14, 2019

--

Following on from a couple of my previous articles, I would like to both reinforce the ideas I laid out in them, as well as consolidate those ideas. In an article[^] from October 2017 I described a pattern I use for designing and implementing RESTful APIs, specifically with regards to implementing RESTful GET APIs. In an article[^] from July 2018 I described the principle of reducing the client surface area, and how this leads to cleaner, simpler and less complex code, particularly with regards to implementing RESTful APIs.

Where I currently work, we have a library of RESTful ASP.NET Web APIs that our web and mobile applications consume. These cover many different types of query as they are used in many different ways by the particular applications. For example the mobile app (which is aimed at fleet drivers) fetches data for the currently signed-in user, their latest mileage updates, their account manager, journeys they have made etc. The web application fetches data relating to users, roles, permissions, documents etc.

These are all GET methods that perform a variety of different queries against different data types. When designing the client API surface required for all these APIs I wanted to make them all consistent, irrespective of what data was being returned, or what query filters were being specified.

To clarify the problem a little further, I wanted to use the same client API for all data types e.g. mileage, user, company, journey etc. Further to this, I wanted the way in which the data was queried to be consistent. Example queries are listed below.

- Fetch me the mileage data for this user
- Fetch me the mileage data for this date range
- Fetch me the journey data for this date
- Fetch me the journey data for this user
- Fetch me the permissions for this user
- Fetch me the documents for this user

These are all queries that work on different data (mileage data, journey data, permissions data, documents data) and interrogate the data in different ways (by user, by date). Crucially, I wanted all of these queries to map onto a single GET API for consistency, and to reduce the complexity of the client (by reducing the client facing API to one API instead of multiple APIs). Reducing the client facing API is the principle of reducing the surface area of the client.

I finally came up with the following API design.

- I have a single controller with a GET method that accepts two parameters.
- The first parameter is a string that designates the type of query e.g. “getmileagebyuser”, “getjourneybydate” etc
- The second parameter is a serialised query object that contains the values needed to query (or filter) the data e.g. the user ID, the date or whatever filters are required to satisfy the request.
- All queries must return their data as a serialised string (which the client can de-serialise back into an object).

For the purposes of clarity the code examples used here have omitted error checking, logging, authentication etc to keep the code as simple as possible. In my own library of RESTful APIs I have separated out the requests made by the mobile app from those made by the web app. I therefore have two controllers, each with a single GET method that does all the heavy lifing of fulfilling the many different query requests. I have created a different controller for each type of client so as to prevent the controllers from bloating. You can separate out the requests any way you want. If you don’t have many queries in your application, then you could simply place all of these query requests in a single GET method in a single controller. That is obviously a design decision only the developer can make.

The controllers are called MobileTasksController and WebTasksController. For the purposes of this article I will focus on the latter controller only, although they both employ the same design pattern that I am about to describe.

First let’s define our basic controller structure.

Hide Copy Code

public class WebTasksController : BaseController
{
public WebTasksController()
{
}
public string WebGetData(string queryname, string queryterms)
{
}
}

You will need to decorate the WebGetData() method for CORS to allow the clients to make requests from your GET method.

Hide Copy Code

[HttpGet]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public string WebGetData(string queryname, string queryterms)
{
}

Enable CORS with the appropriate settings for your own particular application.

As we can see, the WebGetData() method has two parameters.
- queryname is a string that designates the type of query e.g. “getmileagebyuser”, “getjourneybydate” etc
- queryterms is a serialised query object that contains the values needed to query (filter) the data e.g. the user ID, the date or whatever filters are required to satisfy the query request

Here’s the class that I use for passing in the query filters.

Hide Copy Code

[DataContract]
public class WebQueryTasks
{
[DataMember]
public Dictionary<string, object> QuerySearchTerms { get; set; }
/// <summary>
/// Default constructor
/// </summary>
public WebQueryTasks()
{
this.QuerySearchTerms = new Dictionary<string, object>();
}
}

At its core it comprises a dictionary of named objects. By implementing a dictionary of objects this allows us to pass in filters for any type of data e.g. dates, ints, strings etc. We can also pass in as many filters as we need. We can therefore pass in multiple filters e.g. fetch all the journeys for a specific user for a specific date. In this example, we pass in two filters.

- The user ID
- The date

Once the query is serialised we have the following string which is then passed as the second parameter to the RESTful GETmethod.

Hide Copy Code

{"QuerySearchTerms":{"email":"test@mycompany.co.uk"}}

By implementing our queries in this way makes for very flexible code that allows us to query our data in any way we want.

Hide Copy Code

var user = GetUser(emailaddress);
WebQueryTasks query = new WebQueryTasks();
query.QuerySearchTerms.Add("userid", user.Id);
query.QuerySearchTerms.Add("journeydate", Datetime.Now);
string queryterms = ManagerHelper.SerializerManager().SerializeObject(query);

The WebGetData() method then needs to deserialise this object and extract the filters from within. Once we have extracted the filters we can then use them to fetch the data as required by the request.

Hide Copy Code

WebQueryTasks query = ManagerHelper.SerializerManager().DeserializeObject<WebQueryTasks>(queryterms);
if (query == null || !query.QuerySearchTerms.Any())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest, new HttpError("Unable to deserialise search terms.")));
}

The core of the WebGetData() method is a switch statement that takes the queryname as its input. Then, depending on the type of query, the method will extract the necessary filters from the WebQueryTasks parameter.

The names of the queries are stored as constants but could equally be implemented an an enum if preferred. We don’t want to have to hard-code the names of our queries into the method, so any approach that separates these is fine.

In the example below there are two queries. One returns company data for a specified user. The second returns company data for a specified company ID. In each case the code follows the same pattern.

- select the appropriate case statement in the switch
- extract the filters from the query
- invoke the appropriate backend service to fetch the date using the extracted filters (after firstly checking that the filter(s) are not empty)
- serialise the data and return it to the client

Hide Copy Code

object temp;
string webResults;
switch (queryname.ToLower())
{
case WebTasksTypeConstants.GetCompanyByName:
webResults = this._userService.GetQuerySearchTerm("name", query);
temp = this._companiesService.Find(webResults);
break;
case WebTasksTypeConstants.GetCompanyById:
webResults = this._userService.GetQuerySearchTerm("companyid", query);
int companyId = Convert.ToInt32(webResults);
temp = this._companiesService.Find(companyId);
break;
default:
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest, new HttpError($"Unknown query type {queryname}.")));
}

We then need to serialise the results and return these to the client.

Hide Copy Code

var result = ManagerHelper.SerializerManager().SerializeObject(temp);
return result;

In the production version of this controller, I have implemented many more queries in the switch statement, but for clarity I have only implemented two for the purposes of this article.

Here is the full code listing.

Hide Expand

Copy Code

[HttpGet]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public string WebGetData(string queryname, string queryterms)
{
WebQueryTasks query = ManagerHelper.SerializerManager().DeserializeObject<WebQueryTasks>(queryterms);
if (query == null || !query.QuerySearchTerms.Any())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest, new HttpError("Unable to deserialise search terms.")));
}
object temp;
string webResults;
switch (queryname.ToLower())
{
case WebTasksTypeConstants.GetCompanyByName:
webResults = this._userService.GetQuerySearchTerm("name", query);
temp = this._companiesService.Find(webResults);
break;
case WebTasksTypeConstants.GetCompanyById:
webResults = this._userService.GetQuerySearchTerm("companyid", query);
int companyId = Convert.ToInt32(webResults);
temp = this._companiesService.Find(companyId);
break;
default:
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest, new HttpError($"Unknown query type {queryname}.")));
}
var result = ManagerHelper.SerializerManager().SerializeObject(temp);
return result;
}

Just to repeat, for the purposes of this article, the method above has had all error checking, logging, authentication etc removed for the sake of clarity.

I have implemented this pattern in all my GET APIs to great success. It is very flexible and allows me to query the data in multiple ways as neccesary. It also allows the client code to be simpler too, by reducing the client area (the client only needs to interact with a single endpoint / controller), and enforces consistency by ensuring that all queries are similar to one another (they must all pass in two parameters — the first designating the query type, the second containing the query filters).

This pattern of API design achieves all the following benefits
- Simpler server side code by producing substantially less code due to the generic nature of the pattern
- Simpler client side code by only having a single endpoint to interact with
- High degree of flexibility by allowing the APIs to filter the data any way the application requires
- Consistency by ensuring that all requests to the RESTful API are the same

I have been using this pattern in my own RESTful APIs for several years, including several production mobile apps (that are available in the stores) and line-of-buiness web apps. With the pattern in place, I can quickly and easily add new RESTful APIs. This makes adding new services to the apps more timely, and makes the process of adding value to the apps much quicker and simpler.

Feel free to take this idea and modify it as neccessary in your own RESTful APIs.

--

--

Dominic Burford
Zero Equals False

A father, cyclist, vegetarian, atheist, geek and multiple award winning technical author. Loves real ale, fine wine and good music. All round decent chap.