5. We have been here before
• CORBA, DCOM
• SOAP, WSDL
• DTOs
• JSON
6. 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) { }
}
7. The ASP.NET Web API Starter Tutorial
public 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
8. ServiceStack
public class ReqstarsService : Service
{
public List<Reqstar> Get(AllReqstars request)
{
return Db.Select<Reqstar>();
}
}
9. NancyFX
public class SampleModule : Nancy.NancyModule
{
public SampleModule()
{
Get["/"] = _ => "Hello World!";
}
}
13. 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
14. 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
21. The smallest thing that is actionable
{
"description" :"There is a hole in my bucket"
}
22. Define a vocabulary for things of interest
{
"issue" : {
"description" :"There is a hole in my bucket"
}
}
application/json application/vnd.myapirespones+json application/vnd.issue+json
23. Beware of structural changes
{
"issues" : [
{
"description" :"There is a hole in my bucket"
}
]
}
24. Just enough data to solve the problem
{
"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"
}
25. Why do they need that data?
{
"description" :"The font is too big",
"applicationName" : "Wordament",
"applicationVersion" : "1.2.0.2392",
"environment_OSVersion" : "NT4.0",
"environment_MemoryFree" : "10MB",
"environment_DiskSpaceFree" : "100TB",
}
26. Attribute Groups
{
"description" :"The font is too big",
"applicationName" : "Wordament",
"applicationVersion" : "1.2.0.2392",
"environment" : { "osVersion" : "NT4.0",
"memoryFree" : "10MB",
"diskSpaceFree" : "100TB"
}
}
27. Attribute Groups for multiple instances
{
"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"},
]
}
28. {
"description" :"The font is too big",
"reportedByUser" : { "name" : "Bob Bing",
"email" : "bob@acme.com",
"twitter" : "@bobbing",
"dateHired" : "2001-01-21"
}
}
Attribute Groups for related data
29. {
"description" :"The font is too big",
"reportedByUser_url" : "http://api.acme.com/users/75"
}
Linking related data
30. {
"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
33. Meet application/vnd.collection+json
{
"collection": { "links": [],
"items": [
{
"data": [
{ "name": "Title",
"value": "rntttLearning from Noda Time: a case study in API design and open source (good, bad and ugly)rntt“ },
{ "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"
}
}
34. 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
- Developer advocate for Runscope.
- Cloud based solutions for API performance monitoring
Microsoft MVP
Book
Just one part of API design.
designing representations aka HTTP response.
- Versioning sucks
So many ways to do it
Starting to hear the message, don’t version
Roy says middle finger
Layer7 say don’t.
The problem is we all make mistakes.
- Why do we think we need it
- Alternative
- Examples
One reason we think we need versioning is because we do
Objects over the wire
Pass object graph
Infrastructure converts it
Hope the client understands it
Pre-arranged agreement between client/server
The details are key to avoiding versioning.
But Web APIs are not the same as local APIs
Objects over the wire. The idea is that you construct some kind of object graph in your favourite language in your web application, that contains the data that you want to deliver to the clients. And then you pass that object graph to some piece of infrastructure code that converts it into a wire format that hopefully will be consumable by the client. The challenge is, for this to work, there has to be some kind of pre-arranged agreement between sender and receiver. The details of that agreement are the key to avoiding versioning.
- Trying to do this since the mid 1990’sCORBA, DCOM
- When the web won, SOAP was invented
simplified to DTOs
- REST was rediscovered and redefined to use JSON to send objects over the wire
We have been trying to do this since the mid 1990’s. CORBA, DCOM were attempts to allow access to remote objects.
When it was recognized that the web has won, SOAP was invented to try and do Objects over the wire on HTTP
Some smart people realized that “objects over the wire” was never really going to succeed so they simplified to DTOs over the wire.
Types over the wire, but sharing types creates coupling. SOAP used a similar concept but called them data contracts
Only works if you have tight control over both sides of the wire.
JSON has helped. It makes the easy stuff really easy. But the more work we do in JSON the more we are starting to see the re-invention of the complexity. JSON-Schema, JSON Pointer, JSON Path, patch formats, namespacing.
So what does “objects over the wire” look like in code, in the current incarnation.
- The default behavior is to return CLR types
- let the framework decide how to convert it to representation on the wire
In this example we move from returning a native CLR data type to a custom object. Product
Note IHTTPActionResult
Starts to break down when the information we are trying to send is not in the payload
- Returning a object limits how we can craft the response.
- not limited to MS products
Even the really cool frameworks
It is a very common pattern.
Every framework provider will tell you, “oh but you can customize the response if you want”
…but that’s not where they lead you.
It is also not a problem that is limited to the .net space.
This python example is slightly better, because at least we know it is going to be sent as a JSON representation
and the mapping from objects to JSON is fairly straight forward
And just as one last example, rails does the same thing.
So what, you say…
What’s the problem with objects over the wire?
Even with DTOs, you only have indirect control over the serialization process
Properties – address … suite
Stringify has a replacer.
Being aware of what are the rules of your representations is absolutely critical.
When you know it is a rule, you start to think about the long term effects and the cross platform concerns.
When you are “just serializing your objects as JSON” you get a false sense of security.
How you handle links in your server implementation is your business, how you format them on the wire is everyone’s business
I suspect that one of the reasons JSON is so much more popular on the web is that it produces a much cleaner format when serializing objects.
MS XML serializers produce ugly results by default. MS have three different XML serializers and use two different JSON serializers
What’s a DOM….
- Here is just one example of using the JSON.NET Jobject and dynamic.
- There are many ways to do this.
- helper methods allow you to establish conventions in your representation formats.
- Instead of domain objects to DTOs,
- Do domain objects/linq projections to DOM.
- You define your own conventions instead of a serializer doing it for you.
And maybe you don’t even have to define your own conventions, Maybe the conventions of an existing media type meet your needs
Before we start to explore representations
Performance – Headers can be processed and interpreted without having to interpret the entire request body. Cross cutting concerns
Timing – Headers arrive first and can actually be used to abort the reception of the body, or prepare for the body, whilst downloading bytes
Naming – don’t use x-, do use company- for custom headers
Content - Links, json, Unicode?
Often at the root of an API. Another example : Atom service document
Objects include everything.
By not doing so, your force client developers to be more careful with their client code because they cannot assume a property will be there.
Makes it easier to get rid of stray, unused properties
Media Types – define the scope of vocabulary
Sometimes you might want to make something a collection, even though today there is only one.
- Attributes can be added to the root object
- Consider the use-case, not the server object model.
- You can always add, it’s much hard to take away.
- There are reasons to group attributes, until you have a reason, probably not worth it. Structural changes are tricky.
- Clients shouldn’t depend on order
- Name it based on most natural conventions for the format
- Consider issues like character content. Don’t rely on the library to do the right thing.
- Consider the usage of attribute values when deciding on their type.
- Should it be ready to display, or ready to interpret. Consider cultural issues, like formatting, language.
- How much semantics does the client really need? Is it going to do calcs on the memory free?
Grouping together can have a variety of advantages
saved a few bytes
potentially more human readable
allows definition of mandatory fields within a group, without the group itself being mandatory
group becomes a candidate for linking instead of embedding.
- Consider volatility and reusability of data.
- beware of embedding short lived data inside long lived data.
It might map to an object, it might not.
Groups are useful for supporting mulitple instances of a set of data.
JSON requires changing that to be an array.
Whereas it is not essential in XML.
This can be a significant source of breaking changes.
The related information may be useful for the client, but when updating an issue, we would not want the client to
// update the contents off the reportedByUser. They might change the entire object, but not the contents of the object.
// Related information can also be included by providing a link.
// Links can be very handy for pointing at information that changes rarely and can easily be cached locally.
// There are many ways of representing links. Sometimes a link is presented as an object.
Distinction between what is here and what is elsewhere