HATEOAS stands for “Hypermedia as the Engine of Application State” and it is one of the possible constraints that you can place on a REST compliant API. Essentially what it means is that your API is as navigable as a normal website, with hyperlinks leading to other resources. The focus of this blog is not HATEOAS itself – instead focusing on an implementation of it our team recently used for our project’s API.
An example of HATEOAS is shoes. Say you have an endpoint that gives you back a single pair of shoes when you give it an ID. With this pair of shoes you can have related things like what people frequently bought with them, or what laces they pair with. In addition to the shoes themselves, those extra resources are what your API’s payload would link to. A more in depth description of HATEOAS itself can be found here.
Chapter 1: Choosing a format
First and foremost, our project team had to choose the format that we’d like to support for HATEOAS. As of right now, there isn’t a standard format that’s been accepted, but there are a few out there. The biggest options out there (at the time we worked on it) seemed to be:
• Collection+JSON
• Hypertext Application Language (HAL)
• …or you could create your own format
While growing your own solution is always an option, there are several other formats that already exists, so not having a lot of prior experience with this we chose to look into other options.
Looking back now, I see that there are a few other formats that we didn’t consider at the time of implementation such as JSON-LD and SIREN. This is mostly to document our experiences as we developed our API with HATEOAS, so take it with a grain of salt and don’t use it as a definitive answer as to which way is the best.
Collection+JSON is good at representing collections, but at first glance seemed to be a bit verbose in giving links to everything else. Since the majority of our endpoints were giving back a single resource, this probably was going to be a bit much so we looked into other formats.
HAL looked to have a decent spec laid out (linked above) and had an easy to understand way of formatting links. How HAL works is that it groups everything into a resource, and that resource has its own properties and links section. Resources can also have resources inside of them, or “embedded” resources. Embedded resources can be found in an “_embedded” section, and links can be found in a “_links” section. Since links and resources are in different sections, you can find the link for your particular resource by looking at its “rel” or relation property. Think of it like a dictionary, where the key is the link and the value is the resource. For a more in depth explanation, examples, and pretty pictures, check out their spec. It has also had an Internet-Draft published here.
All these things made it easiest to understand (for us), so we started looking into how to implement the HAL format on our API.
Chapter 2: Choosing a package – we first tried WebApi.Hal
As any good developer does, we first looked to see if anyone has invented this particular wheel already so that we could use that instead of writing it again. So we had a look on Nuget for what was out there for our Web API project. The one with the most downloads (at the time of our analysis) was WebApi.Hal.
WebApi.Hal has a simple way of implementing HAL, where the models you’re passing back to the end user implement their “Representation” class. What this does is gives access to a “Rel” and “Href” property. These are the properties that will be used when generating a link to “self”, or the link to the resource you’re providing itself. As long as you just set those two properties, you are in business for a self link. Any other resources that you want to link to, or embedded resources in your payload you will have to generate, are overridden in a “CreateHypermedia” function. You have access to the “Links” array for your resource, so you just add to this array any other links (rel + href combos) your resource has. For example, if in your data for shoes you have an ID to the laces that fit that shoe, you could create a link to that resource with the ID. The sample below is a simple example from a project WebApi.Hal set up for people to play around with:
public class BeerStyleRepresentation : Representation { public int Id { get; set; } public string Name { get; set; } public override string Rel { get { return LinkTemplates.BeerStyles.Style.Rel; } set { } } public override string Href { get { return LinkTemplates.BeerStyles.Style.CreateLink(new {id = Id}).Href; } set { } } protected override void CreateHypermedia() { Links.Add( LinkTemplates.BeerStyles.AssociatedBeers.CreateLink(new {id = Id}) ); } }
You’ll notice a “LinkTemplates” class is used in the example. This is one way to store the links for your API. In the example project it’s a static class that holds each individual link.
For collections of data, instead of implementing “Representation”, you implement one of their other classes for collections – such as “SimpleListRepresentation”, which then in turn implements “Representation”. These list representations simply take a List of your Models in their constructor. So you end up having a separate model for your Lists of data, but it works out pretty easy. Another cool thing to come with WebApi.Hal is the other List Representations that allow for plug-and-play pagination. Continuing with the last example, here is what a list of those looks like:
public class BeerStyleListRepresentation : SimpleListRepresentation<BeerStyleRepresentation> { public BeerStyleListRepresentation(IList<BeerStyleRepresentation> beerStyles) : base(beerStyles) { } protected override void CreateHypermedia() { Href = LinkTemplates.BeerStyles.GetStyles.Href; Links.Add(new Link { Href = Href, Rel = "self" }); } }
Aside from your models, the only thing you need to set up is in your start up configuration. You need to use their JsonHalMediaTypeFormatter as your Formatter. How it works is it interrupts the OnSerialize method to inject your links for you. If you’re all-in with this Hal implementation you can .Clear() all the other formatters in your config and just use theirs. Otherwise when calling the API you’ll have to send the Accept header with a value of “application/hal+json” so it knows which format you want back. A common trip up point is this formatter not being used, so your JSON ends up with empty HAL sections.
All in all things went pretty well with this package. The documentation was fairly good and the learning curve wasn’t so bad. We did run into one issue once we sent our API over to our QA group. It happens if you have a collection of resources, but the collection only has one item in it. Normally, if a collection has more than one item both the _embedded section and the _links section would serialize in an identical format as an array in JSON. If there is one item in the collection however, the _embedded section’s collection will still serialize as an array, but the _links section will just be a single object—not an array. This happens due to the nature in which WebApi.Hal groups resources, in that they don’t have the original data type for the link they’re creating, so if it’s a group of more than one it serializes as an array, otherwise it’s an object. This caused a hiccup in QA’s automation, as they were expecting the same data type when they programmatically traversed it. I came up with a bit of a hack to fix this in the source code, but that’s not really the spirit of using a package, so we started looking into other solutions.
Chapter 3: Choosing a package – next we tried HalKit
HalKit was the next package we checked out. The documentation was sparse, but the core way it built things out was much more sound, so it wouldn’t have the same issue WebApi.Hal did. Everything implements what they call “Resource” this time, even the collections. All links and embedded sections are then added to a single dictionary, which then keeps them together. Instead of using a formatter, it uses Data Contracts to serialize your end payload with links. Instead of having separate properties in your models for Rel and Href, you attribute the properties you want to be in the embedded (“Embedded” attribute) or links (“Rel” attribute) section. The Rel and Embedded attribute can also be attached to the same property. Below is a very simple example of this:
public class Order : Resource { public int OrderId { get; set; } public string CustomerName { get; set; } public DateTime OrderDate { get; set; } [Embedded("Items")] [Rel("Items")] public List<Product> Items { get; set; } }
The Resource class has a “SelfLink” property you have to set if you want to use the self link.
HalKit was definitely the more difficult to work through since it had less documentation and we couldn’t get the sample project working. At the time of writing it only had about ~700 downloads, so there wasn’t a lot of information out there about it. Once we got the hang of it however, it was easy to use. A few issues cropped up when we started seeing its results.
Upon first test, we noticed that instead of just having a link object with an Href, the link was an object with Href, title and isTemplated that serialized. They are part of the Hal spec, but we aren’t using them. Since one of our endpoints sends back a list of 26k resources with links (we’ll do pagination soon, promise) those two small extra properties quickly become 52k extra properties and make the payload bigger and even harder to read or parse.
The second issue we noticed a bit later was something that WebApi.Hal was doing for us that we never thanked it for. In our links we were putting a “~” at the beginning and the old package was resolving that to the right part of the URL that it needed to be depending on the environment. The new package (through no fault of its own) wasn’t doing this for us, so every link that was sent through just ended up how it was. Root is different for our different environments, so this was going to be a problem.
We fixed both of these issues by having our own Custom Link class that only had the Href property, and would resolve URLs for you as you passed them in. This had minimal impact on our implementation of HalKit and didn’t mess with HalKit directly in any way, but it isn’t the clearest what’s happening if you’re coming into our project fresh.
Chapter 4: Success! (and what’s next)
Both packages had their own issues that could be fixed, but we decided to go with HalKit because we could fix the issue outside of the package itself.
One thing that we found was an issue outside the packages themselves was with the API UI tool Swagger. We use Swagger on our endpoints to help us with documentation and to put a nice UI on them. One of the things Swagger does is expose a “model” of the endpoint you’re hitting. The issue came in that each package would mess up Swagger’s view of the model so it wouldn’t show the same thing that was being sent back to the end user. Our solution to this was to build out mock models for Swagger to look at. This can be done by decorating the controller endpoint with the following attribute:
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(ProductResource))]
“ProductResource” in this case being the mock model (of Product) that is used nowhere else. Not the best solution to the problem, as we now have to maintain two separate but seemingly identical models, but it’s where we’re at.
If you’re looking into using something for your API know that all of this is still very new and no real format or standard has been adopted to do it. Keep your options open. Even building your own solution shouldn’t be out of the question—in fact I would consider it to be a top option.
Leave a Comment