Monday, June 1, 2015

HATEOAS

HATEOAS: the much ignored tenet of REST services.  What does it mean?  What does it do?  How do you do it?  Those are the three questions I can hopefully answer in this post.

What does it mean?  This one is easy: Hypertext As The Engine Of Application State.

Great, so what does it actually mean?  It means you use links (yes, the same kind you put on a webpage in an anchor tag) to tell the client what it can do next.  Ergo, you use Hypertext (links) as the Engine of Application State (to tell the client what it can do).

The basic idea behind HATEOAS is that the server should be able to tell the client(s) what comes next in the process, based on the current state of the resource being returned.  Allow me to use an analogy.  Big shocker here, but I'm going with the old building a house analogy.

So let's say you're building a house.  Right from the beginning you have a resource that has a state.  For the sake of argument (and because I've never actually built a house), we'll say the resource is House and at the beginning of the process the State of House is Not Started (you can envision an empty lot if that helps).  When House has a State of Not Started, the Construction Crew (i.e. the client) may only take a few actions.  They can Change Plans, Lay Foundation, or Scrap It.  There's a ton of stuff that will come later, but right away they can't do those things (like Frame).

Once the Construction Crew chooses an action from the three available, the State of House changes.  So let's say the Construction Crew chooses Lay Foundation.  The State of House is now Foundation Laid and there are new actions that can be taken: Change Plans, Frame, Landscape, or Scrap It.  You'll notice (hopefully) that they have two of the same options.  When House is in Foundation Laid or Not Started, the actions Change Plans and Scrap It are both available.

As you can see the State of House dictates what Construction Crew can do next.  If House is the resource returned from the server, it's pretty easy to see that the server gets to dictate what steps are available for the client to take.  If the server decides that new steps should be available at different steps the service can be modified to include those links in the response.  Going back to the House example, if the service decided that Install Basketball Hoop should be available when the State of House is Foundation Laid, they can do that.  The client (Construction Crew) can then choose to do that or not.  See, the server isn't telling the client what to do next, only what they can do next.

So that should answer "What does it mean" and "What does it do", but the really important question is "How do you do it".  Again, there's a short answer and a long answer.  The short answer is that you return a links property in your response that contains the actions the client can take.  The longer answer involves code.  Keep in mind that this is just how I've done it one time so it's not The Way or anything, just a suggestion.

It's pretty easy to do the JSON version of this because serializing a class to a JSON object using JSONConvert is really simple. What I do is create a base class for every class that will be returned to a client, usually named something obvious like BaseViewModel:
   1:  public class BaseViewModel
   2:  {
   3:      [XmlIgnore]
   4:      public virtual List<LinkViewModel> Links
   5:      {
   6:          get { return new List<LinkViewModel>(); }
   7:      }
   8:  }

And the LinKViewModel is pretty basic as well:
   1:  [XmlRoot("link")]
   2:  public class LinkViewModel
   3:  {
   4:      [XmlAttribute("rel")]
   5:      public string Rel { get; set; }
   6:   
   7:      [XmlAttribute("href")]
   8:      public string Href { get; set; }
   9:  }

Once we have that, it's a single line to render the output into a usable JSON result:
   1:  JsonConvert.SerializeObject(value);

All of that leads to a result that looks like this:
{"Comments":"Happy","Id":6,"Links":[{"Rel":"view","Href":"/api/grade/get/6"},{"Rel":"update","Href":"/api/grade/update"},{"Rel":"appeal","Href":"/api/appeal/add"}],"State":0,"StudentId":123,"Value":0.0}
With that result I can use a little client-side code (in this case it's pure JavaScript) to get the links from the response and see what I can do:
   1:  function getLink(object, name) {
   2:      var links = getLinks(object);
   3:      for (var i = links.length; --i >= 0;) {
   4:          if (links[i].rel != null && links[i].rel == name) {
   5:              return links[i].href;
   6:          } else if (links[i].Rel != null && links[i].Rel == name) {
   7:              return links[i].Href;
   8:          }
   9:      }
  10:   
  11:      return null;
  12:  }
  13:          
  14:  function getLinks(object) {
  15:      var links = [];
  16:      if (object != null && object.link != null && object.link.constructor === Array) {
  17:          for (var i = object.link.length; --i >= 0;) {
  18:              links.push(object.link[i]);
  19:          }
  20:      } else if (object != null && object.link != null) {
  21:          links.push(object.link);
  22:      } else if (object != null && object.Links != null && object.Links.constructor === Array) {
  23:          for (var j = object.Links.length; --j >= 0;) {
  24:              links.push(object.Links[j]);
  25:          }
  26:      }
  27:      else if (object != null && object.Links != null) {
  28:          links.push(object.Links);
  29:      }
  30:   
  31:      return links;
  32:  }

So there you go. HATEOAS and how to do it.

No comments:

Post a Comment