This blog explores what a service following the REST architecture should look like. Considering that such an API is, in theory, supposed to use the HTTP verbs and be focused on resources, its interface will be markedly different from your typical RPC-style API. So, as we design the service, we will compare the REST approach with a more traditional RPC or SOAP approach.
Throughout my blog, we will be working on a service for managing tasks. It’s not terribly exciting, I know; however, the lack of domain excitement will let you focus on the technical aspects of the service. Designing a RESTful interface is trickier than you might think, and you will need to reprogram your brain to some degree to go about modeling such an API.
The fact that this is more work up front certainly doesn’t mean you shouldn’t follow this path. As briefly covered in the previous blog, there are many benefits to the REST architecture. But it will take some work to realize those benefits. Creating a REST API is not as simple as just converting your RPC methods into REST URLs, as many like to imagine. You must work within the constraints of the architecture. And, in this case, you must also work within the constraints of the HTTP protocol because that will be your platform.
Here’s what you’ll learn about in this article:
- Leonard Richardson’s maturity model for REST
- Working with URIs and resources
- Working with HTTP verbs
- Returning appropriate HTTP status codes
Let’s get started.
From RPC to REST
In November 2008, a fellow by the name of Leonard Richardson created a maturity model for REST. A maturity model, by definition, is a map that guides the user into ever-increasing levels of compliance with a given definition, architecture, or methodology. For example, the model called Capability Maturity Model Integration (CMMI) was created as a process-improvement approach to help organizations (typically, software organizations) improve performance and increase efficiencies. The model contains five levels, where each successive level is designed to provide the user or organization more process efficiency over the previous level.
Richardson’s REST Maturity Model (RMM) provides service API developers the same type of improvement map for building RESTful web services. His model, in fact, starts at level 0 with a RPC-style interface, and then progresses up through three more levels - at which point you’ve achieved an API interface design that is, at least, according to Roy Fielding, a pre-condition for a RESTful service. That is, you cannot claim to have a RESTful service if you stop at levels 0, 1, or 2 of the RMM; however, it’s certainly possible to screw things up to the extent that you don’t have a RESTful service at level 3, either.
Figure 1 summarizes the levels in the RMM.
Figure 1. Diagram of Richardson’s REST Maturity Model
XML-RPC and SOAP
At Level 0, the API resembles most SOAP services. That is, the interface is characterized by having a single URI that supports only a single HTTP method or verb. You’ll learn more about the available verbs in a bit; but for now, just know that HTTP provides a small set of known verbs that must be used if you intend to conform to and capitalize on the protocol.
Suppose, as mentioned in my first article, that you want to build a task-management service, and you need to provide a way to create new tasks through a new API. If this were a Level 0 SOAP-based service, you might create a WCF service class called TaskService; and on it you might create a new method called
CreateTask(). This method might take a request message that includes a task title, task category, perhaps a status, and so on.And the method’s response message would likely include, among other things, a system-generated task number.
You also might create a method for fetching the details of a task. So, on your TaskService class, you might add a method called
GetTask(). This new method would likely take as input a task ID of some kind, and then return a task object - serialized as XML.
To round out the TaskService class, you might also add the methods
CompleteTask(). Each of these would be designed to take in appropriate XML request messages and return some kind of response message.
The REST Maturity Model - and indeed the work published by Roy Fielding - provides three distinct web-related attributes of an API that help you position yourself to be RESTful with HTTP:
- Unique URIs to resources
- Consistent use of HTTP verbs
- “Hypermedia as the engine of application state” (HATEOAS)
Let’s examine the pretend TaskService service interface using these three attributes (see Table 1).
Table 1. Task Service at Level 0 on the RMM
As you can see, each operation or method on the service looks the same - when looked at from the point of view of the Web. And the service doesn’t look and feel very Web-like. For example, whether fetching task 123 or task 456, the URI is the same. In fact, it is also the same URI used to create a task, update a task, complete a task, and so on. There’s no sense of resource or resource addressability in our URI - that is, there’s no URI that points directly to a specific task or list of tasks.
This example also does not utilize HTTP verbs as intended. This was discussed a bit in my blog, and you’ll learn about this in more detail later; however, every action available in the API is essentially custom-made. To be RESTful on HTTP, you need to avoid creating custom actions and instead support actions that are consistent with HTTP. In other words, you need to use GET, PUT, POST, and DELETE (to name the primary actions).
And finally, clients are required to know all of the available actions ahead of time. This means there is an implicit binding between client and server, in that the caller is dependent on a contract and a given set of actions from the service. Again, this does not feel very Web-like. When you browse to a public website, all you are required to remember is the root address. From there, everything else is discoverable and linked to other elements via hypermedia (i.e., links and forms). Indeed, hypermedia is the engine of application state. You can transition from one state to the next (where the state machine is a web site or the broader Internet) based solely on links and forms. You are not required to remember or know ahead of time the specific addresses for all of the pages you intend to traverse.
You are also not required to remember every field that must be filled out on a form when submitting a request (e.g., placing an order or signing up for a magazine subscription). Essentially, the server dictates all of the relationships, all of the URIs, and all of the forms - without you needing any prior knowledge. So, if any of these properties change, you likely wouldn’t even notice or care. This is because we, the clients, have an implicit understanding with web sites: they will guide us through available resources and provide all the information we need in order to make any changes or requests.
As you’ll see shortly, this attribute of HATEOAS is key to a service’s RESTfulness; however, it is often overlooked as it requires a significant shift in thinking from the traditional RPC-style interface design.
URIs and Resources
As noted briefly in blog, building a RESTful interface means you end up with an API that is very resource-centric. As such, you need to intentionally design the interface with resources being at the center. Unlike RPC-style interfaces, where arbitrary service methods (i.e., the verbs) and their associated request and response messages rule the day, a REST interface will revolve around the resources (i.e., the nouns). The actions available to those resources are constrained by the use of HTTP. This is why you must map the available HTTP verbs into the API; you don’t have the freedom to create other actions or verbs.
This concept is central to a REST design. So let’s examine what the TaskService might look like if it were to be a level 1 on the RMM. Table 2 shows how each individual resource is addressable by a unique URI.
Table 2. Task Service at Level 1 on the RMM
But you still must rely on specific messages for operations. In other words, the caller can’t differentiate between the two different operations available with the /api/tasks URI - unless the caller already has the contract. You’re still using only the POST HTTP verb, so the request message itself dictates the desired action.
You must look beyond URIs and their resources to the actions needed by the service. These actions will help you identify the HTTP verbs you need to use. Continuing to follow our example, there’s no such thing as a CreateTask HTTP verb. In fact, there’s not even a Create verb. If you’re going to follow the REST architecture and the HTTP protocol, you must choose from the verbs available in that protocol, namely:
Intuitively, you can quickly eliminate GET and DELETE for the CreateTask action. But what is the difference in intent between PUT and POST? As shown in Table 3, PUT is designed to create or replace a resource with a known identifier - and hence a known unique URI. You use a POST when the system is generating the new resource’s identifier.
Technically speaking, the REST architecture is agnostic about any specific protocol. That includes the HTTP protocol. In other words, all you need is a protocol that provides a language and mechanism for describing both states (i.e., representations) and state changes. However, since my blog is about building a REST service with ASP.NET, you’ll focus on REST with HTTP. Fortunately, the HTTP protocol itself covers most of what you need. Again, Table 3 illustrates the intended use of the verbs within REST.
Table 3. Using HTTP verbs with the Task Resource
| || |
List of tasks, including URIs to individual tasks
Get a specific task, identified by the URI
| || |
Replace the entire collection of tasks
Replace or create the single task identified by the URI
| || |
Create a new single task, where its identifier is generated by the system
Create a new subordinate under the task identified by the URI
| || |
Delete the entire collection of tasks
Delete the tasks identified by the URI
Let’s walk through some important concepts with this mapping. First, the exact meaning of each of the four verbs is dependent on the URI. So even though you have only four verbs, you actually have eight different actions available to you. The difference lies in whether the URI defines a collection or a unique element.
Second, when creating new instances of the resource (e.g. a new task), PUT is used with a unique URI in the scenario where the caller generates the new resource’s identifier before submitting the request to the server. In Table 3, the PUT action is used with a unique element URI to create a new task with the specific identifier, 1234. If instead the system is to generate the identifier, then the caller uses the POST action and a collection URI. This also ties into the concept of idempotency.
The PUT and DELETE methods are said to be idempotent; that is, calling them over and over will produce the same result without any additional side effects. For example, the caller should be able to call the DELETE action on a specific resource without receiving any errors and without harming the system. If the resource has already been deleted, the caller should not receive an error. The same applies to the PUT action. For a given unique resource (identified by an element URI), submitting a PUT request should update the resource if it already exists. Or, if it doesn’t exist, the system should create the resource as submitted. In other words, calling PUT over and over produces the same result without any additional side effects (i.e., the new task will exist in the system per the representation provided by the caller, whether the system had to create a new one or it had to update an existing one).
The GET action is said to be safe. This is not idempotent, per se. Safe means that nothing in the system is changed at all, which is appropriate for HTTP calls that are meant to query the system for either a collection of resources or for a specific resource.
It is important that the idempotency of the service’s GET, PUT, and DELETE operations remain consistent with the HTTP protocol standards. Thus, every effort should be made to ensure those three actions can be called over and over without error.
Unlike the other three actions, POST is not considered to be idempotent. This is because POST is used to create a new instance of the identified resource type for every invocation of the method. Where calling PUT over and over will never result in more than one resource being created or updated, calling POST will result in new resource instances - one for each call. This is appropriate for cases where the system must generate the new resource’s identifier, as well as return it in the response.
As you model your task-management service, you will need to map each resource with a set of HTTP actions, defining which ones are allowed and which ones aren’t supported.
Now let’s take a new look at the task service. This time around, you’ll use the available HTTP verbs, which will put you at level 2 on the RMM (see Table 4).
Table 4. Task Service at Level 2 in the RMM
At this point, the service is utilizing unique URIs for individual resources, and you’ve switched to using HTTP verbs instead of custom request message types. That is, each of the PUT and POST actions mentioned previously will simply take a representation of a task resource (e.g., XML or JSON). However, the client must still have prior knowledge of the API in order to traverse the domain and to perform any operations more complex than creating, updating, or completing a task. In the true nature of the Web, you should instead fully guide the client, providing all available resources and actions via links and forms. This is what is meant by “hypermedia as the engine of application state.”
As you look at Tables 3 and 4, you can see that certain GET operations will return collections of resources. One of the guiding principles of REST with HTTP is that callers make transitions through application state only by navigating hypermedia provided by the server. In other words, given a root or starting URI, the caller should be able to navigate the collection of resources without needing prior knowledge of the URI scheme. Thus, whenever a resource is returned from the service, whether in a collection or by itself, the returned data should include the URI required to turn around and perform another GET to retrieve just that resource.
Here’s an example of an XML response message that illustrates how each element in the collection should contain a URI to the resource:
<?xml version="1.0" encoding="utf-8"?> <Tasks> <Task Id="1234" Status="Active" > <link rel="self" href="/api/tasks/1234" method="GET" /> </Task> <Task Id="0987" Status="Completed" > <link rel="self" href="/api/tasks/0987" method="GET" /> </Task> </Tasks>
It is typically appropriate to return only a few attributes or pieces of data when responding with a collection, such as in the preceding example. Now the caller can use the URI to query a specific resource to retrieve all attributes of that resource. For example, the Tasks collection response (as just shown) might only contain the Task’s Id and a URI to fetch the Task resource. But when calling GET to get a specific Task, the response might include TaskCategory, DateCreated, TaskStatus, TaskOwner, and so on.
Taking this approach can be a little trickier when using strongly typed model objects in .NET (or any other OO language). This is because we need to define at least two different variants of the Task type. The typical pattern is to have a TaskInfo class and a Task class, where the TaskInfo class exists only to provide basic information about a Task. The collection might look like this:
<?xml version="1.0" encoding="utf-8"?> <Tasks> <TaskInfo Id="1234" Status="Active" > <link rel="self" href="/api/tasks/1234" method="GET" /> </TaskInfo> <TaskInfo Id="0987" Status="Completed" > <link rel="self" href="/api/tasks/0987" method="GET" /> </TaskInfo> </Tasks>
And the single resource might look like this:
<?xml version="1.0" encoding="utf-8"?> <Task Id="1234" Status="Active" DateCreated="2011-08-15" Owner="Sally" Category="Projects" > <link rel="self" href="/api/tasks/1234" method="GET" /> </Task>
Utilizing two different types like this is not a requirement for REST or any other service API-style. You may find that you don’t need to separate collection type definitions from other definitions. Or, you may find that you need many more than two. It all depends on the usage scenarios and how many different attributes exist on the resource. For example, if the Task resource included only five or six attributes, then you probably wouldn’t create a separate type for the collection objects. But if the
Task object were to include 100 or more attributes (as is typical in any real-life financial application), then it might be a good idea to create more than one variation of the
Within the realm of HATEOAS, you also want to guide the user as to the actions available on a resource. You just saw how you can use a
<link> element to provide a reference for fetching task details. You can expand this concept to include all available resources and actions. Remember, when browsing a web site, a user needs to have prior knowledge only of the root address to traverse the entire site. You want to provide a similar experience to callers in the API.
Here’s what a full HATEOAS-compliant XML response might look like for the
<?xml version="1.0" encoding="utf-8"?> <Tasks> <TaskInfo Id="1234" Status="Active" > <link rel="self" href="/api/tasks/1234" method="GET" /> <link rel="users" href="/api/tasks/1234/users" method="GET" /> <link rel="history" href="/api/tasks/1234/history" method="GET" /> <link rel="complete" href="/api/tasks/1234" method="DELETE" /> <link rel="update" href="/api/tasks/1234" method="PUT" /> </TaskInfo> <TaskInfo Id="0987" Status="Completed" > <link rel="self" href="/api/tasks/0987" method="GET" /> <link rel="users" href="/api/tasks/0987/users" method="GET" /> <link rel="history" href="/api/tasks/0987/history" method="GET" /> <link rel="reopen" href="/api/tasks/0987" method="PUT" /> </TaskInfo> </Tasks>
Note that the links available to each task are a little different. This is because you don’t need to complete an already completed task. Instead, you need to offer a link to reopen it. Also, you don’t want to allow updates on a completed task, so that link is not present in the completed task.
I want to offer a disclaimer and a word of warning for the topic of links in REST messages. You find that, over the past several years, the debate over how the HTTP verbs are supposed to be used can be quite heated at times. This debate also extends into how to best design URIs to be most RESTful - without degenerating into SOAP-style API.
For example, in the
TaskXML you just looked at, it specifies the “reopen” link as a
/api/tasks/0987URI. It also specifies the “complete” link as a
/api/tasks/1234 URI. These approaches are neither specified by the REST architecture, nor are they even agreed upon by the folks that practice REST. And for whatever reason, people on various sides of the debate tend to get worked up about their way of doing things.
Instead of using a PUT against the resource URI for the “reopen” action, you could instead use a PUT against a URI like /api/tasks/0987/reopen. I tend to lean away from this approach, as it pushes you closer to specifying actions instead of resources (for the URI). However, I also think it’s a bit unrealistic to assume you can accommodate all available actions on something like a Task object with only four HTTP verbs. Indeed, there are a few more verbs you can use, including PATCH, HEAD, and OPTIONS. But even so, the set of available verbs is limited, and the REST architecture dictates that you don’t add to those verbs. So at some point, you need to make a judgment call as to how to implement various actions on the Task object. The important thing is to conform as closely to HTTP standards as possible.
The use of the DELETE verb is also hotly debated. Most enterprise applications don’t allow the caller to really delete a resource. More often, a resource is merely closed, inactivated, hidden, and so on. As such, it might seem reasonable to not waste one of your precious few verbs on an action that you never even allow, when instead you could use it for the “close” action.
As with most endeavors in the world of software, the devil’s in the details. And you can usually find 101 ways to implement those details if you look hard enough. My advice here is to simply do the best you can, don’t be afraid to be wrong, and don’t get stuck in an infinite loop of forever debating the very best approach to follow. Think, commit, and go.
You can now complete the table of task resources and operations using the three concepts you’ve learned from the RMM:
- URIs and resources
- HTTP verbs
Table 5 illustrates the task service under a more ideal RESTful design. That is, it shows the things you can do to make the service self-describing (i.e., related information and available operations are given to the caller via links contained in the service’s responses). Again, following the RMM isn’t sufficient in itself to being able to claim your service is a REST service. That said, you can’t claim compliance with REST without following it, either.
Table 5. Task Service at Level 3 in the RMM
There is one last bit of guidance to discuss before wrapping up this exploration of REST.
HTTP Status Codes
So far in this blog, you’ve learned about the constraints of the REST architecture that led to creating an API where resources are the message of choice; where every resource and every action on a resource has a unique URI; where, instead of creating custom methods or actions, you’re limiting yourself to the actions available with HTTP; and, finally, where you’re giving the caller every action available on a given resource. All of these constraints deal with calls made by the caller. The last thing to discuss deals with the messages you send back from the server in response to those calls.
In the same way that you are constrained to using only the verbs available with HTTP, you are also constrained to using only the well-known set of HTTP status codes as return “codes” for your service calls. That is not to say you can’t include additional information, of course. In fact, every web page you visit includes an HTTP status code, in addition to the HTML you see in the browser. The basic idea here is simply to utilize known status codes in the response headers.
Let’s look first at a subset of the available HTTP status codes. You can find the complete official specification here. In this section, you will only be examining a small subset of these codes. Table 6 lists the most common status codes and their descriptions in the context of a RESTful API.
Table 6. A List of Common HTTP Status Codes
| || |
All is good; response will include applicable resource information, as well
| || |
Resource created; will include the Location header specifying a URI to the newly created resource
| || |
Same as 200, but used for async; in other words, all is good, but we need to poll the service to find out when completed
| || |
The resource was moved; should include URI to new location
| || |
Bad request; caller should reformat the request
| || |
Unauthorized; should respond with an authentication challenge, to let the caller resubmit with appropriate credentials
| || |
Access denied; user successfully authenticated, but is not allowed to access the requested resource
| || |
Resource not found, or, caller not allowed to access the resource and we don’t want to reveal the reason
| || |
Conflict; used as a response to a PUT request when another caller has dirtied the resource
| || |
Server error; something bad happened, and server might include some indication of the underlying problem
For example, assume a caller submitted the following HTTP request:
GET /api/tasks/1234 HTTP/1.1
The service should respond as follows (this is the raw HTTP response):
HTTP/1.1 200 OK Content-Type: application/xml <Task Id="1234" Status="Active" DateCreated="2011-08-15" Owner="Sally" Category="Projects" > <link rel="self" href="/api/tasks/1234" method="GET" /> <link rel="users" href="/api/tasks/1234/users" method="GET" /> <link rel="complete" href="/api/tasks/1234" method="DELETE" /> <link rel="update" href="/api/tasks/1234" method="PUT" /> </Task>
Suppose now the caller is using a POST request to create a new task:
POST /api/tasks HTTP/1.1 Content-Type: application/xml <Task Status="Active" DateCreated="2012-08-15" Owner="Jimmy" Category="Projects" >
The service should respond with a 201 code and the new task’s URI (assuming the call succeeded):
HTTP/1.1 201 Created Location: /api/tasks/6789 Content-Type: application/xml <Task Id="6789" Status="Active" DateCreated="2012-08-15" Owner="Jimmy" Category="Projects" > <link rel="self" href="/api/tasks/6789" method="GET" /> <link rel="owner" href="/api/tasks/6789/owner" method="GET" /> <link rel="complete" href="/api/tasks/6789" method="DELETE" /> <link rel="update" href="/api/tasks/6789" method="PUT" /> </Task>
The main point here, which is consistent with the topics discussed throughout this article, is to utilize the HTTP protocol as much as you can. That is really the crux of REST with HTTP: you both use HTTP and allow yourself to be constrained by it, rather than working around the protocol.
In this blog, you explored various characteristics of a service API that must exist before you can claim you are RESTful. Remember that adherence to these characteristics doesn’t automatically mean your service qualifies as a REST service; however, you can at least claim its service interface qualifies as such.
You also walked through Leonard Richardson’s maturity model for REST services and used the model as a platform for comparing a RESTful service to something more SOAP- or XML-RPC in nature. This allowed you to see that SOAP services do not capitalize on various aspects of HTTP, as your REST services should.