crafting evolvable api responses
DESCRIPTION
Create evolvable responses to avoid versioning your Web API for as long as possible.TRANSCRIPT
Crafting Evolvable API Responses
Journey
• Versioning sucks, let’s just not do it• Why we think we need it?• How can we avoid it?• Example time
Objects over the wire
We have been here before
• CORBA, DCOM• SOAP, WSDL• DTOs• JSON
The ASP.NET Web API Project Template
public class ValuesController : ApiController { // GET api/values public IEnumerable<string> Get() { return new string[] { "value1", "value2" };}
// GET api/values/5 public string Get(int id) { return "value"; }
// POST api/values public void Post([FromBody]string value) { }
// PUT api/values/5 public void Put(int id, [FromBody]string value) { }
// DELETE api/values/5 public void Delete(int id) { } }
The ASP.NET Web API Starter Tutorialpublic class ProductsController : ApiController { //…
public IEnumerable<Product> GetAllProducts() { return products; }
public IHttpActionResult GetProduct(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { return NotFound(); } return Ok(product); } }
http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
ServiceStack
public class ReqstarsService : Service { public List<Reqstar> Get(AllReqstars request) { return Db.Select<Reqstar>(); } }
NancyFX
public class SampleModule : Nancy.NancyModule{ public SampleModule() { Get["/"] = _ => "Hello World!"; }}
Python Flask
@app.route('/todo/api/v1.0/tasks', methods=['GET'])def get_tasks(): return jsonify({'tasks': tasks})
Rails
# Returns the resource from the created instance variable # @return [Object] def get_resource instance_variable_get("@#{resource_name}") end
But, what’s the problem?
Which objects to map?
• Domain objects • How do we hide content we don’t want to expose?• How do we create different views of data?• Changes to domain model cause changes to API
• DTOs• whole lot of busy work• Only indirect control over serialization process
Automatic Serialization
• All the properties• Non standard data types: datetime, timespan• Does null mean unspecified, or explicitly null?• Empty collection or no collection• Capitalization• Links• Loops• Changes to behavior in the frameworks
Take back control
Use a DOM to build your document
dynamic jspeaker = new JObject();jspeaker.name = speakerInfo.Name;jspeaker.bio = speakerInfo.Bio;
dynamic links = new JObject();
dynamic iconLink = new JObject();iconLink.href = speakerInfo.ImageUrl;links.icon = iconLink;
dynamic sessionsLink = new JObject();sessionsLink.href = SessionsLinkHelper.CreateLink(request, speakerInfo).Target;links[LinkHelper.GetLinkRelationTypeName<SessionsLink>()] = sessionsLink;
jspeaker["_links"] = links;return new DynamicHalContent(jspeaker);
What to do with your new found freedom?
Anatomy of an HTTP representation
• Status line• Headers• Body
Headers
• Performance• Timing • Naming• Content• Compression
Let’s build some payloads
{"description" :"There is a hole in my bucket"
}
The smallest thing that is actionable
{"issue" : {
"description" :"There is a hole in my bucket"}
}
Define a vocabulary for things of interest
application/json application/vnd.myapirespones+json application/vnd.issue+json
{"issues" : [
{"description" :"There is a hole in my bucket"
}]
}
Beware of structural changes
{"description" :"There is a hole in my bucket","stepsToReproduce" : "Pour water in bucket. Lift bucket off ground.
Look for water dripping", "dateReported": "2012-04-21T18:25:43-05:00"
}
Just enough data to solve the problem
{"description" :"The font is too big","applicationName" : "Wordament","applicationVersion" : "1.2.0.2392","environment_OSVersion" : "NT4.0","environment_MemoryFree" : "10MB","environment_DiskSpaceFree" : "100TB",
}
Why do they need that data?
{"description" :"The font is too big","applicationName" : "Wordament","applicationVersion" : "1.2.0.2392","environment" : { "osVersion" : "NT4.0",
"memoryFree" : "10MB","diskSpaceFree" : "100TB"
}}
Attribute Groups
{"description" :"The font is too big","history" : [
{"status" : "reported", "date" :"2014-02-01"},{"status" : "triaged", "date" :"2014-02-04"},{"status" : "assigned", "date" :"2014-02-12"},{"status" : "resolved", "date" :"2014-02-19"},
]}
Attribute Groups for multiple instances
{"description" :"The font is too big","reportedByUser" : { "name" : "Bob Bing",
"email" : "[email protected]", "twitter" : "@bobbing", "dateHired" : "2001-01-21"
}}
Attribute Groups for related data
{"description" :"The font is too big","reportedByUser_url" : "http://api.acme.com/users/75"
}
Linking related data
{"description" :"The font is too big","Links" : [
{ "href" :"http://api.acme.com/users/75", "rel": "reportedByUser" },{ "href" :"http://api.acme.com/users/24", "rel": "assignedToUser" }
] }
Multiple Links
Learn from others
application/hal+json
application/vnd.collection+json
application/vnd.siren+json
application/vnd.github.v3+json
application/vnd.heroku+json
application/json-ld
application/json-home
application/http-problem
application/activity+json
application/vnd.mason+json
{"description" :"The font is too big","_embedded" : {
"reportedByUser" : { "name" : "Bob Bing",
"email" : "[email protected]","_links" : { "self" : {"href" :"http://api.acme.com/users/75"}}
}
}
Meet application/hal+json
{ "collection": { "links": [], "items": [ { "data": [ { "name": "Title", "value": "\r\n\t\t\tLearning from Noda Time: a case study in API design and open source (good, bad and ugly)\r\n\t\t“ }, { "name": "Timeslot", "value": "04 December 2013 16:20 - 17:20“ }, { "name": "Speaker", "value": "Jon Skeet“ } ], "links": [ { "href": "http://conference.hypermediaapi.com/speaker/6", "rel": "http://tavis.net/rels/speaker" }, { "href": "http://conference.hypermediaapi.com/session/133/topics", "rel": "http://tavis.net/rels/topics" } ], "href": "http://conference.hypermediaapi.com/session/133" } ], "query": [], "template": { "data": [] }, "version": "1.0" }}
Meet application/vnd.collection+json
Wrap up
• Understand the limitations of “objects over the wire”• Consider taking back control of your representations• Think in terms of messages, instead of objects• Build software that is designed to survive change
Image Credits
• Package - https://flic.kr/p/3mrNyn• Freedom - https://flic.kr/p/4vwRDw• Treasure map - https://flic.kr/p/7jDJwi• Handshake - https://flic.kr/p/nbAu8Y• Telephone - https://flic.kr/p/7Q8bMd• Blindfolded Typing - https://flic.kr/p/53Q3JE• Magic Trick - https://flic.kr/p/7T8zk5• Donut machine - https://flic.kr/p/anncxf• GT-R Steering Wheel - https://flic.kr/p/fDUSDk• Anatomy - https://flic.kr/p/6bfUZn• Shapes - https://flic.kr/p/3aKUAq• Payloaders - https://flic.kr/p/dTU9sN• Birds on a Wire - https://flic.kr/p/4YdfK