Designing, implementing, and maintaining APIs for the Web today is more than a challenge - it has become an imperative. Kyle Fuller, a core member of CocoaPods & Pelican, explains how REST is an architecture that allows web developers to anticipate change as applications evolve.
What is REST? (0:00)
Today, I’m not going to be talking about features in Swift, but more about other concepts. We’re now exploring different programming paradigms, and it’s a good time to learn about other things. So, I’ll be talking about embracing change with REST.
REST stands for Representational State Transfer. It’s a software architecture that leads us to build really good APIs. I assume that no one is building APIs in Swift yet, so I’ll focus on consuming APIs. The Representational State Transfer really lets us embrace change. It’s an architecture, and is not protocol specific. Most people use HTTP with REST, but you can use it with whatever you want.
Now, REST is a bunch of constraints on how you should develop your APIs. It was originally created by Roy Fielding in his dissertation when he was developing HTTP. REST is really designed around how the Web works. The main theme around REST is that it gives us a good way to anticipate change.
“Move fast and break things.”
- Mark Zuckerberg
You might have heard of this quote by Mark Zuckerberg. But, what happens when we do that with APIs? Well, unfortunately this usually means you have to redeploy the clients. It’s going to be quite a sad experience for the users. You can be left with an app which isn’t going to work. With the current Apple review process for updates, you could be waiting up to two weeks. This process really doesn’t work, so we can’t just move fast and break things.
Facebook doesn’t actually do this with their API. What they do is they freeze their API every two years. The current API is guaranteed to work for up to two years from now, which is a long time. That doesn’t really let them innovate. They can’t change their API, and have to leave it how it is at the moment.
Move Fast & Break Nothing (2:33)
Wouldn’t it be great if we could move fast and break nothing? Well, we can with REST because it’s designed to allow and promote change. On the Web, we do that with creating new websites. Many people think REST is about these things: CRUD, pretty URLs, JSON, HTTP verbs, or even routing. REST has nothing to do with those. It is actually about representations and hypermedia. REST is also about an out-of-bounds specification.
Building an API to a certain specification will really couple your client to the server. REST is not about exposing your database. You might think that we design apps this way, but we don’t. What we do is design implementations this way. So when you think of building an API, you might think of building one for a to-do app. Let’s look at how the model would work. We’re using a relational database, so we have the id, a title, and a status. Well, we’re already thinking of implementation details, which is going to lead to our API being designed by API details. That is only going to lead to tight coupling to our implementation from the client and server.
Instead, we can do this with states. Our todo app would be better expressed with a collection resource. That gives us two features. It gives us the ability to create a new ”Todo”, as well as a list of them. We also get the collection of all the resources, which we can call a “Todo item”. These would let us do things like mark something as completed, delete it, change it, and so on.
If we are designing our APIs around state, how do we transfer state from the server to the client? What we do is transfer representations of the state by exposing our application of logic for an API - hence, Representation State Transfer.
Transfer State Transitions 4:35)
How is it that we transfer state transitions? Well, for this, use something called HATEOAS. This acronym stands for Hypermedia as the Engine of Application State. It’s one of the key constraints to REST, and it really allows us to unleash the power of being able to change our API.
HATEOAS is built around an idea of relations and transitions, or actions. As I’ve said, it’s a constraint. That means it’s not an option, and it’s not an ideal. You either do it, or you’re not doing REST. You can’t have evolvability if clients have controls baked into their design at deployment. Controls are to be learned on the fly, and that is what hypermedia enables: change over time.
This is done really well on the Web. You don’t have to change your browser every time someone updates their website. Suppose you have a website that has this HTML representation to tell you what you can do and where you can go from there. You follow links and transitions. If we change our URI, we don’t break anything. Instead, we can still get there, because we are following a link to it instead of hardcoding that knowledge. If all we used was REST, we could do this without breaking our clients.
<html>
<body>
<h1>Things you can do</h1>
<ul>
<li><a href="/questions/new" rel="create">Make a new question</a></li>
</ul>
</body>
</html>
<!-- Change the URI -->
<li><a href="/polls/new" rel="create">Make a new question</a></li>
How would someone be constructing a question the bad way? In that API, we would construct a “new question” URL, with our hardcoding knowledge about it. Then we’d submit a form using our pre-existing knowledge of how that form might look. If we’re creating a question on a poll application, we might think that we are making a post-request to a specific endpoint. We know that the poll is a question with some choices. When we submit that, it may not work anymore, because the server could have evolved and actually changed.
Now with hypermedia, we are offered to create a question. So with this API, you can create a question. It tells you how you’ll do that and give you a form to actually process, fill in, and then submit. We’d expect that to work. That’s exactly how you browse the Web. Clients can have the same benefits by doing this, too.
Example: Polls (6:56)
I’ve built an API which is for polls, and I’m going to work through how it looks. Here are the resources. To start with, we have a resource called Questions. This gives us the ability to create a question, and also list questions. For each question, we have the ability to delete it. We also have a relation to its choices. Each choice allows us to vote on it, or it may not.
How would you represent this in a client? Well, JSON isn’t really good for this. There is no place where you say that there are all the state transitions in a JSON document. People have extended JSON, and there are a dozen of different formats you can uses. Two of the most popular are HAL and Siren.
Siren (7:46)
A Siren document is just a JSON document, but it has semantic meaning on certain keys, one of which is links. Here we can express that the current resource has a link to the next resource, using a relation called “next”. We could also say this resource has a reference to a “choice”.
// Relation "next"
{
"links": [
{ "rel": ["next"], "href": "/questions?page=2" }
]
}
// Relation “choice”
{
"links": [
{ "rel": ["choice"], "href": "/questions/1/choices/1" }
]
}
You can also embed your resource. In this embedded entity, you have the relation “choice”, which has properties “choice”: Swift
and “votes”: 22
. Of course, we still have the URI.
{
"entities": [
{
"links": {
{ "rel": ["self"], "href": "/questions/1/choices/1" }
},
"rel": ["choices"],
"properties": {"choice": "Swift", "votes": 22}
}
]
}
We can also express forms this way. We can say that a choice action has a vote choice on it. This is the URI you need to do to vote on this, and this is the HTTP method you’d use. It also expresses what we can do - we can send it a question and choice.
{
"actions": {
"create": {
"href": "/questions",
"method": "POST",
"fields": [
{ "name": "question" },
{ "name": "choices" }
]
}
}
}
We can also suggest that you send a form as “www-form-urlencoded”, which is a content type used to send this kind of information. But you could change that, right? You just update to JSON, and then your application would still work, hopefully. This really lets us change things, because our client isn’t hardcoded with this knowledge. It is instead downloading it on the fly.
{
"actions": {
"create": {
"href": "/questions",
"method": "POST",
"fields": [
{ "name": "question" },
{ "name": "choices" }
],
/* "type": "application/x-www-form-urlencoded" */
"type": "application/json"
}
}
}
This gives us the ability to change implementation details. We can change the URIs of our resources, the HTTP methods used, and the content type. We can also change fields used in forms. We do this by teaching our clients the semantic meanings of our domain. Let’s say we have an application which understands the semantic meaning of a question, a poll, and a choice. We don’t have this concept of implementation details.
Demo (10:00)
For this specific polls example, I’ve built an application which has the semantic meaning of polls. It has no idea of a specific API, other than the base URI. In my terminal I’m running the actual HTTP server to the port. If I pull down on the app screen to refresh, you can see in the terminal that it has made a request to the root, and then to /questions
.
There aren’t many features in this app. We can’t slide to delete and we can’t add a new poll; all we can do is view a poll and its options. Wouldn’t it be great if we could start adding new features to our API? It understands the semantic meaning behind deletion and add, but it’s not yet supported in our API. So, I’ve implemented this specific server, which has this support that hasn’t yet been exposed to the clients.
I can just change a value to enable a creation feature. So now if I pull to refresh, the client sees that the server offers the ability to create a new choice, and poll. The plus button just appeared when I refreshed. I did not change any code in my application, and all of a sudden I have this form. The server told us that we can create a new poll and add choices to it. In a similar manner, we can also enable things like deletion and the ability to vote on a question. After we refresh the application, we can now slide the items to delete because it has understood from the server that this feature is there now.
The benefit to this is that we can actually change lots of implementation details. We could actually change the URI used for questions, to polls. We can change the URL for each individual question to polls/id
, and same thing for our choices. Then what do you think will happen if we refresh our client? This time, it has actually requested polls. That’s because the root endpoint has described to us how to access the questions in the API. We don’t have knowledge of how it works, but we’re told on the fly.
Representor (12:57)
I’m doing this with a tool I built called Representor. It’s a small Swift library that has a unified interface to various hypermedia formats. This allows you to just consume without thinking about these different formats. I’m using Siren, which is what I described earlier in this specific API. My application doesn’t really have the knowledge of that, it just uses this library to interface with that.
Each resource has its representation, and it has the available transitions onto it. So for example, we can see if there is a “create”
action on a resource. If we don’t, we can gracefully handle the lack of it. That means that we can remove features from our API without breaking our client.
if let transition = representor.transitions["create"] {
} else {
// Gracefully handle the lack of this transition
}
We can introspect the form. We have this property called attributes
, which gives us all of the things we can give it. We could even add new fields and the client would understand them, if it already knows about them when it’s looking at this. But if it’s not there yet, then it knows not to show them. We can also get things like method
, uri
, and even perhaps a suggestedContentType
.
if let transition = representor.transitions["create"] {
transition.method
transition.uri
transition.suggestedContentType
}
Once we’re ready, we can actually just perform it. We’re going to perform this request without any knowledge of how the API works, without any URI, and without the method. It’s all in that transition that we downloaded from the server at run time. And, we did not know about the implementation details. This really lets us evolve our client, because we can just change things and our application will just work.
request(transition, attributes:[
"question": "Favourite programming language?",
"choices": [
"Swift",
"Python",
]
])
Resources (14:26)
I’m not saying everyone should use REST, but I definitely think you should take a look at it. It really does provide us some really powerful features. I’ve built up a collection of resources about how people are doing it. You’ll be able to learn more information about this because it’s quite a huge subject.
- “REST APIs must be hypertext-driven” by Roy Fielding
- Representor, a Swift library for building & consuming Hypermedia messages
- Roy Fielding on Versioning
- Polls API
- rivr-rest, a library for building REST APIs with rivr
- Solving FizzBuzz with Hypermedia
Q&A (14:43)
Q: I’ve run into an app that uses the same pattern of encoding URLs. How do you make it work if you want to cache those models in the client, but the URLs change?
Kyle: The client doesn’t have knowledge of the actual URIs itself. It gets them dynamically, so each resource has it’s own URI, or a representation of it. When the client asks for the root endpoint, the root endpoint says,“Well, this API offers this specific feature, say a list of questions.” You just follow the link from that, like you would on a website. If the link has changed, the client would just follow the different link.
Q: Does this mean you can’t really cache resources locally? Are they visible if you are offline?
Kyle: You could look up this resource and say that you understand all the semantics behind what attributes are on it, and we could install that on a local database. Then we could store the URI that we used to get that inside our database. So, you could make a local representation of it.
Q: What happens if you add a new property to one of the resources that is getting transferred? How do older clients work with the new information that you have added?
Kyle: What you should always do is, build your clients around seeing if a value that it understands is there. If it isn’t, then don’t do anything with it. If it’s some concept you don’t understand, or you don’t understand the semantic meaning of it, there’s not really much you could do. A browser is really good at this, because it can show you everything that you can do, even things it doesn’t understand. It has no domain semantics behind every website, but it uses a generic way to describe how it works, and you could build an application around that.
For example, in the poll application that I showed, creating a poll is completely powered by the form that the server told us. If I added a new form field, it would just show up in that application. It wouldn’t understand what it means, but it would show it. You might not want to go down that approach, because you might want more fine-grain control and not just show things that you don’t understand. It all depends on what you’re trying to achieve.
Receive news and updates from Realm straight to your inbox