Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mobile APIs: More than just object.to_json

Mobile APIs: More than just object.to_json

Almost all apps rely on APIs, no app is an island, entire of itself. We want to keep our apps slick and simple, pushing complex logic to the server, allowing iOS devs to focus on shiny new user experiences. At realestate.com.au, we're building simpler apps backed by smarter microservices using REST, hypermedia and HATEOAS, not just object.to_json. This session will demonstrate how to quickly build iOS apps that discover, consume and navigate these services using HAL JSON and Objective C libraries we love like AFNetworking and ReactiveCocoa in a way 'future you' will be thankful for. There will be at least one obligatory Australian reference to Crocodile Dundee.

Stewart Gleadow

November 15, 2013
Tweet

More Decks by Stewart Gleadow

Other Decks in Technology

Transcript

  1. Mobile APIs: More than just object.to_json Stewart Gleadow @stewgleadow REA

    Group http://www.flickr.com/photos/markselliott/48699538/
  2. Web services and REST ›❯ Discoverable iOS APIs ›❯ Simple

    apps, smart backends ›❯ So where do I start ›❯
  3. Web services and REST ›❯ Discoverable iOS APIs ›❯ Simple

    apps, smart backends ›❯ So where do I start ›❯
  4. POST /InStock HTTP/1.1 Host: www.example.org Content-Type: application/soap+xml; charset=utf-8 Content-Length: 299

    SOAPAction: "http://www.w3.org/2003/05/soap-envelope" <?xml version="1.0"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"> <soap:Header> </soap:Header> <soap:Body> <m:GetStockPrice xmlns:m="http://www.example.org/stock"> <m:StockName>IBM</m:StockName> </m:GetStockPrice> </soap:Body> </soap:Envelope>
  5. Level 0 Level 1 Level 2 Level 3 The swamp

    of POX URIs and Resources
  6. Level 0 Level 1 Level 2 Level 3 The swamp

    of POX URIs and Resources HTTP verbs and status codes
  7. Level 0 Level 1 Level 2 Level 3 The swamp

    of POX URIs and Resources HTTP verbs and status codes Hypermedia controls
  8. “What needs to be done to make the REST architectural

    style clear on the notion that hypertext is a constraint?” Roy Fieldin - http://roy. biv.com/untan led/2008/rest-apis-must-be-hypertext-driven
  9. “What needs to be done to make the REST architectural

    style clear on the notion that hypertext is a constraint?” Roy Fieldin - http://roy. biv.com/untan led/2008/rest-apis-must-be-hypertext-driven “Is there some broken manual somewhere that needs to be fixed?”
  10. “What needs to be done to make the REST architectural

    style clear on the notion that hypertext is a constraint?” Roy Fieldin - http://roy. biv.com/untan led/2008/rest-apis-must-be-hypertext-driven “Is there some broken manual somewhere that needs to be fixed?” “Please try to adhere to them or choose some other buzzword for your API.”
  11. Web services and REST ›❯ Discoverable iOS APIs ›❯ Simple

    apps, smart backends ›❯ So where do I start ›❯
  12. { results : [ { ... listing 1 ... },

    { ... listing 2 ... } ], _links : { next : { name : “Next page of search results”, href : “http://services.realestate.com.au/search?page=2” }, self : { name : “Refresh this page of search results”, href : “http://services.realestate.com.au/search?page=1” } } }
  13. POST “http://example.com/session” { email : "dundee", password : "password" }

    HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd” } Sign in request Sign in response
  14. POST “http://example.com/session” { email : "dundee", password : "password" }

    HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd” } Which actions are available now? Where do I perform those actions? Sign in request Sign in response
  15. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; Sign in & get details from an iOS client
  16. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  17. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  18. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  19. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  20. Response HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links :

    { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } }
  21. Response HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links :

    { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } } Which actions are available now? Where do I perform those actions?
  22. Response HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links :

    { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } } Which actions are available now? Where do I perform those actions?
  23. Response HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links :

    { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } } Which actions are available now? Where do I perform those actions?
  24. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.user_id]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  25. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.detailsUrl = response[@"_links"][@"details"][@"href"]; } failure:nil]; [self.manager GET:self.detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  26. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.detailsUrl = response[@"_links"][@"details"][@"href"]; } failure:nil]; [self.manager GET:self.detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  27. NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password"

    }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.detailsUrl = response[@"_links"][@"details"][@"href"]; } failure:nil]; [self.manager GET:self.detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil]; Sign in & get details from an iOS client
  28. GET “http://example.com/services” ... HTTP/1.1 200 OK { _links : {

    signIn : { name : “Authenticate a user”, href : “http://example.com/session” }, suggest : { name : “Retrieve suburb suggestions matching a string”, href : “http://suggest.realestate.com.au/suggest{?query}”, templated : true }, ... } }
  29. GET “http://example.com/services” ... HTTP/1.1 200 OK { _links : {

    signIn : { name : “Authenticate a user”, href : “http://example.com/session” }, suggest : { name : “Retrieve suburb suggestions matching a string”, href : “http://suggest.realestate.com.au/suggest{?query}”, templated : true }, ... } }
  30. [self.manager GET:@"http://example.com/services" parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.signInEndpoint =

    response[@"_links"][@"signIn"][@"href"]; self.suggestEndpoint = ... } failure:nil]; Consuming discoverable endpoints
  31. [self.manager GET:@"http://example.com/services" parameters:nil success:^(AFHTTPRequestOperation *op1, NSDictionary *servicesResponse) { NSString *signInEndpoint

    = servicesResponse[@"_links"][@"signIn"][@"href"]; [self.manager POST:signInEndpoint parameters:params success:^(AFHTTPRequestOperation *op2, NSDictionary *signInResponse) { NSString *detailsEndpoint = signInResponse[@"_links"][@"details"][@"href"]; [self.manager GET:detailsEndpoint parameters:nil success:^(AFHTTPRequestOperation *op3, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:^(AFHTTPRequestOperation *op3, NSError *error) { self.status.text = @"Failed to get user details."; }]; } failure:^(AFHTTPRequestOperation *op2, NSError *error) { self.status.text = @"Failed to sign in."; }]; } failure:^(AFHTTPRequestOperation *op1, NSError *error) { self.status.text = @"Failed to configure service"; }]; Chainin multiple requests ets messy
  32. [self.manager GET:@"http://example.com/services" parameters:nil success:^(AFHTTPRequestOperation *op1, NSDictionary *servicesResponse) { NSString *signInEndpoint

    = servicesResponse[@"_links"][@"signIn"][@"href"]; [self.manager POST:signInEndpoint parameters:params success:^(AFHTTPRequestOperation *op2, NSDictionary *signInResponse) { NSString *detailsEndpoint = signInResponse[@"_links"][@"details"][@"href"]; [self.manager GET:detailsEndpoint parameters:nil success:^(AFHTTPRequestOperation *op3, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:^(AFHTTPRequestOperation *op3, NSError *error) { self.status.text = @"Failed to get user details."; }]; } failure:^(AFHTTPRequestOperation *op2, NSError *error) { self.status.text = @"Failed to sign in."; }]; } failure:^(AFHTTPRequestOperation *op1, NSError *error) { self.status.text = @"Failed to configure service"; }]; Chainin multiple requests ets messy
  33. NSString *services = @"http://example.com/services"; RACSignal *userDetailsSignal = [[[[[[self.manager rac_getPath:services] map:^NSString

    *(NSDictionary *response) { return response[@"_links"][@"signIn"][@"href"]; }] flattenMap:^RACSignal *(NSString *signInEndpoint) { return [self.manager rac_postPath:signInEndpoint parameters:params]; }] map:^NSString *(NSDictionary *response) { return response[@"_links"][@"details"][@"href"]; }] flattenMap:^RACSignal *(NSString *detailsEndpoint) { return [self.manager rac_getPath:detailsEndpoint]; }] replayLast]; ReactiveCocoa to the rescue
  34. NSString *services = @"http://example.com/services"; RACSignal *userDetailsSignal = [[[[[[self.manager rac_getPath:services] map:^NSString

    *(NSDictionary *response) { return response[@"_links"][@"signIn"][@"href"]; }] flattenMap:^RACSignal *(NSString *signInEndpoint) { return [self.manager rac_postPath:signInEndpoint parameters:params]; }] map:^NSString *(NSDictionary *response) { return response[@"_links"][@"details"][@"href"]; }] flattenMap:^RACSignal *(NSString *detailsEndpoint) { return [self.manager rac_getPath:detailsEndpoint]; }] replayLast]; ReactiveCocoa to the rescue
  35. Web services and REST ›❯ Discoverable iOS APIs ›❯ Simple

    apps, smart backends ›❯ So where do I start ›❯
  36. http://www.flickr.com/photos/jakecaptive/3205277810 Forcin lo ic to the server What if you

    could only use NSDictionary objects for your domain model?
  37. landSize : { size: 1440, units: “metres squared”, display: “Land

    size: 1440 m²” } Less lo ic in the iOS client!
  38. features: [ { label: “Outdoor Features”, features: [ “Garage: 2”,

    ] }, { label: “Indoor Features”, features: [ “Air Conditioning”, “Alarm System”, ... “Ensuite: 1” ] } ]
  39. propertyFeatures: { display: “\e021 4 \e022 1 \e023 2” }

    Could we format beds, baths, carparks using an icon font?
  40. photo : { thumbnail: ”http://photos.realestate.com.au/100x30/EzMjNhOU”, thumbnail-retina: ”http://photos.realestate.com.au/200x60/EzMjNhOU”, gallery: ”http://photos.realestate.com.au/320x90/EzMjNhOU”, gallery-retina:

    ”http://photos.realestate.com.au/640x180/EzMjNhOU”, detail: ”http://photos.realestate.com.au/480x320/EzMjNhOU”, detail-retina: ”http://photos.realestate.com.au/960x640/EzMjNhOU” } Explicit links for each photo size
  41. photo : { imageServer: ”http://photos.realestate.com.au”, imageId: “EzMjNhOU” } NSInteger width

    = 100, height = 60; photoUrl = [NSString stringWithFormat: @”%@/%dx%d/%@”, photo.imageServer, width, height, photo.imageId]; Don’t template the URI in the iOS client
  42. photo : “http://photos.realestate.com.au/{width}x{height}/EzMjNhOU” CSURITemplate *template = [CSURITemplate URITemplateWithString:photo error:&error]; NSDictionary

    *params = @{ @"width" : @100, @"height" : @60 }; NSString *photoUrl = [template relativeStringWithVariables:params error:&error]; Server-side RFC-6570 URI template
  43. Web services and REST ›❯ Discoverable iOS APIs ›❯ Simple

    apps, smart backends ›❯ So where do I start ›❯
  44. Web services and REST ›❯ Discoverable iOS APIs ›❯ Simple

    apps, smart backends ›❯ So where do I start ›❯
  45. References John Donne, No Man Is An Island http://en.wikipedia.org/wiki/Meditation_XVII Martin

    Fowler, Richardson Maturity Model http://martinfowler.com/articles/richardsonMaturityModel.html Leonard Richardson, Justice Will Take Us Millions Of Intricate Moves http://www.crummy.com/writing/speaking/2008-QCon/act3.html Roy Fieldin - REST APIs Must Be Hypertext Driven http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven Leonard Richardson, API Desi n is Stuck in 2008 http://blog.programmableweb.com/2013/10/07/api-design-is-stuck-in-2008/ Cam Barrie, Desi n your API, Mother %$^ http://vimeo.com/61342270 Mike Kelly, HAL JSON http://stateless.co/hal_specification.html
  46. References Perryn Fowler, Richardson Maturity Model By Example http://prezi.com/1-0gtuokjcqo/rmm-by-example/ The

    Internet En ineerin Task Force (IETF), RFC 6570 - URI Template http://tools.ietf.org/html/rfc6570 Wikipedia, RSDL: RESTful Service Description Lan ua e http://en.wikipedia.org/wiki/RSDL Jim Webber, Savas Parastatidis, Ian Robinson; REST in Practice http://restinpractice.com/book/ Leonard Richardson, Sam Ruby; RESTful Web Services http://shop.oreilly.com/product/9780596529260.do