Designing api for mobile apps – teammates @ polidea – Polidea...



Designing api for mobile apps – teammates @ polidea – Polidea...

0 0


geecon-mobile-api-presentation


On Github wojtekerbetowski / geecon-mobile-api-presentation

Designing api for mobile apps

Gosia niech zrobi grafikę

teammates @ polidea

Maciek

Polidea...

  • makes mobile apps (iOS, Android, WP)
  • develops hardware related stuff
  • contributes to OSS - RoboSpock, Flow, ...
  • supports community
    • Mobile Central Europe
    • Mobile Warsaw
Wojtek

Welcome

to the world of ...

Latency

Latency

PC + WiFi Mobile + WiFi Mobile + LTE Mobile + HSDPA+ Mobile + 2G min 0.993 2 15 42 203 avg 3.435 19 90 60 227 max 30.678 460 811 956 2000 stddev 2.22 17 x 55 84 down 139.90 18.57 14.47 5.73 0.08 up 86.19 18.23 5.73 3.68 0.1

HATEOAS

GET /users/123
{
  "name": "John Doe",
  "age": 25,
  "links": [
    {
      "rel": "self",
      "href": "/users/123"
    },
    {
      "rel": "account",
      "href": "/accounts/987"
    },
    {
      "rel": "address",
      "href": "/addresses/555"
    }
  ]
}
GET /addresses/555
{
  "street": "Sesame",
  "no": 25,
  "zipCode": "12-321",
  "state": "NY",
  "country": "US"
}

Merging responses

GET /users/123
{
  "name": "John Doe",
  "age": 25,
  "country": "US"
}

Expansion

GET /users/123?fields=[name,age,address[country]]
{
  "name": "John Doe",
  "age": 25,
  "country": "US"
}
better for public APIs in contrast to homegrown, efficient expansion mechanism

Connection: Keep-Alive

Throughput

Connection speed market share

Maciek

Data subset

Complete model
{
  "person": {
    "id": 12345,
    "firstName": "John",
    "lastName": "Doe",
    "age": 25,
    "phones": {
      "home": "800-123-4567",
      "work": "888-555-0000",
      "cell": "877-123-1234"
    },
    "email": [
      "jd@example.com",
      "jd@example.org"
    ],
    "dateOfBirth": "1980-01-02T00:00:00.000Z",
    "registered": true,
    "emergencyContacts": [
      {
        "name": "",
        "phone": "",
        "email": "",
        "relationship": "spouse|parent|child|other"
      }
    ],
    "address": {
        "street": "Sesame",
        "no": 25,
        "zipCode": "12-321",
        "state": "NY",
        "country": "US"
    }
  }
}
Trimmed model
{
    "firstName": "John",
    "lastName": "Doe",
    "age": 25,
    "country": "US"
}

Compress data (gzip)

Full: 222910 bytes
GZIP: 32128 bytes (14%)
{
"people": [
    {
        "firstName": "Jason",
        "lastName": "Page",
        "username": "jasonp",
        "isMale": true,
        "phone": "142-808-3743",
        "nid": "12252671714"
    },
    {
        "firstName": "Nathaniel",
        "lastName": "Chaney",
        "username": "nchaney",
        "isMale": true,
        "phone": "481-659-731",
        "nid": "55122958778"
    },
    {
        "firstName": "Alexis",
        "lastName": "Christensen",
        "username": "alexisc",
        "isMale": false,
        "phone": "802-046-8689",
        "nid": "13240667404"
    },
    {
        "firstName": "Sophie",
        "lastName": "Duke",
        "username": "sophied",
        "isMale": false,
        "phone": "316-701-3331",
        "nid": "96071009282"
    },
    {
        "firstName": "Aria",
        "lastName": "Wolf",
        "username": "ariaw",
        "isMale": false,
        "phone": "682-172-2915",
        "nid": "12231830224"
    },
    {
        "firstName": "Sebastian",
        "lastName": "Hogan",
        "username": "sebastianh",
        "isMale": true,
        "phone": "096-033-8930",
        "nid": "65082565494"
    },
    {
        "firstName": "Austin",
        "lastName": "Rice",
        "username": "austinr",
        "isMale": true,
        "phone": "558-871-007",
        "nid": "91102544132"
    },
    {
        "firstName": "Elijah",
        "lastName": "Savage",
        "username": "esavage",
        "isMale": true,
        "phone": "009-900-8985",
        "nid": "10301742394"
    },
    {
        "firstName": "Colton",
        "lastName": "Morris",
        "username": "cmorris",
        "isMale": true,
        "phone": "029-957-8223",
        "nid": "84062095076"
    },
    {
        "firstName": "Juan",
        "lastName": "Bass",
        "username": "jbass",
        "isMale": true,
        "phone": "701-810-3457",
        "nid": "10311068954"
    },
    {
        "firstName": "Brody",
        "lastName": "Reese",
        "username": "breese",
        "isMale": true,
        "phone": "320-610-636",
        "nid": "82112959596"
    },
    {
        "firstName": "Stella",
        "lastName": "Bernard",
        "username": "sbernard",
        "isMale": false,
        "phone": "134-462-1924",
        "nid": "03272935528"
    },
    {
        "firstName": "Isaac",
        "lastName": "Webb",
        "username": "iwebb",
        "isMale": true,
        "phone": "136-000-422",
        "nid": "10231649814"
    },
    {
        "firstName": "Jackson",
        "lastName": "Bryan",
        "username": "jacksonb",
        "isMale": true,
        "phone": "629-900-7729",
        "nid": "83071189716"
    },
    {
        "firstName": "Liam",
        "lastName": "Beard",
        "username": "lbeard",
        "isMale": true,
        "phone": "327-639-010",
        "nid": "86050552116"
    },
    {
        "firstName": "Parker",
        "lastName": "Duffy",
        "username": "pduffy",
        "isMale": true,
        "phone": "484-829-6473",
        "nid": "56122687578"
    },
    {
        "firstName": "Lucy",
        "lastName": "Moody",
        "username": "lucym",
        "isMale": false,
        "phone": "723-670-5321",
        "nid": "77051444320"
    },
    {
        "firstName": "Aaliyah",
        "lastName": "Leonard",
        "username": "aleonard",
        "isMale": false,
        "phone": "005-293-048",
        "nid": "08281195048"
    },
    {
        "firstName": "Brody",
        "lastName": "Norris",
        "username": "brodyn",
        "isMale": true,
        "phone": "789-653-2217",
        "nid": "58050880078"
    },
    {
        "firstName": "Juan",
        "lastName": "Valenzuela",
        "username": "juanv",
        "isMale": true,
        "phone": "060-065-140",
        "nid": "93052612612"
    },
    {
        "firstName": "Jocelyn",
        "lastName": "Collins",
        "username": "jcollins",
        "isMale": false,
        "phone": "010-257-964",
        "nid": "65083166064"
    },
    {
        "firstName": "Bentley",
        "lastName": "Welch",
        "username": "bentleyw",
        "isMale": true,
        "phone": "068-620-269",
        "nid": "09293076518"
    }
]
}
            
Full: 48 bytes
GZIP: 60 bytes (125%)
{
  "firstName": "Arianna",
  "lastName": "Mcknight"
}
            

Expansion

GET /people/
{
"people": [
    {
        "firstName": "Jason",
        "lastName": "Page",
        "username": "jasonp",
        "isMale": true,
        "phone": "142-808-3743",
        "nid": "12252671714"
    },
    {
        "firstName": "Nathaniel",
        "lastName": "Chaney",
        "username": "nchaney",
        "isMale": true,
        "phone": "481-659-731",
        "nid": "55122958778"
    },
    {
        "firstName": "Alexis",
        "lastName": "Christensen",
        "username": "alexisc",
        "isMale": false,
        "phone": "802-046-8689",
        "nid": "13240667404"
    },
    {
        "firstName": "Sophie",
        "lastName": "Duke",
        "username": "sophied",
        "isMale": false,
        "phone": "316-701-3331",
        "nid": "96071009282"
    },
    {
        "firstName": "Aria",
        "lastName": "Wolf",
        "username": "ariaw",
        "isMale": false,
        "phone": "682-172-2915",
        "nid": "12231830224"
    },
    {
        "firstName": "Sebastian",
        "lastName": "Hogan",
        "username": "sebastianh",
        "isMale": true,
        "phone": "096-033-8930",
        "nid": "65082565494"
    },
    {
        "firstName": "Austin",
        "lastName": "Rice",
        "username": "austinr",
        "isMale": true,
        "phone": "558-871-007",
        "nid": "91102544132"
    },
    {
        "firstName": "Elijah",
        "lastName": "Savage",
        "username": "esavage",
        "isMale": true,
        "phone": "009-900-8985",
        "nid": "10301742394"
    },
    {
        "firstName": "Colton",
        "lastName": "Morris",
        "username": "cmorris",
        "isMale": true,
        "phone": "029-957-8223",
        "nid": "84062095076"
    },
    {
        "firstName": "Juan",
        "lastName": "Bass",
        "username": "jbass",
        "isMale": true,
        "phone": "701-810-3457",
        "nid": "10311068954"
    },
    {
        "firstName": "Brody",
        "lastName": "Reese",
        "username": "breese",
        "isMale": true,
        "phone": "320-610-636",
        "nid": "82112959596"
    },
    {
        "firstName": "Stella",
        "lastName": "Bernard",
        "username": "sbernard",
        "isMale": false,
        "phone": "134-462-1924",
        "nid": "03272935528"
    },
    {
        "firstName": "Isaac",
        "lastName": "Webb",
        "username": "iwebb",
        "isMale": true,
        "phone": "136-000-422",
        "nid": "10231649814"
    },
    {
        "firstName": "Jackson",
        "lastName": "Bryan",
        "username": "jacksonb",
        "isMale": true,
        "phone": "629-900-7729",
        "nid": "83071189716"
    },
    {
        "firstName": "Liam",
        "lastName": "Beard",
        "username": "lbeard",
        "isMale": true,
        "phone": "327-639-010",
        "nid": "86050552116"
    },
    {
        "firstName": "Parker",
        "lastName": "Duffy",
        "username": "pduffy",
        "isMale": true,
        "phone": "484-829-6473",
        "nid": "56122687578"
    },
    {
        "firstName": "Lucy",
        "lastName": "Moody",
        "username": "lucym",
        "isMale": false,
        "phone": "723-670-5321",
        "nid": "77051444320"
    },
    {
        "firstName": "Aaliyah",
        "lastName": "Leonard",
        "username": "aleonard",
        "isMale": false,
        "phone": "005-293-048",
        "nid": "08281195048"
    },
    {
        "firstName": "Brody",
        "lastName": "Norris",
        "username": "brodyn",
        "isMale": true,
        "phone": "789-653-2217",
        "nid": "58050880078"
    },
    {
        "firstName": "Juan",
        "lastName": "Valenzuela",
        "username": "juanv",
        "isMale": true,
        "phone": "060-065-140",
        "nid": "93052612612"
    },
    {
        "firstName": "Jocelyn",
        "lastName": "Collins",
        "username": "jcollins",
        "isMale": false,
        "phone": "010-257-964",
        "nid": "65083166064"
    },
    {
        "firstName": "Bentley",
        "lastName": "Welch",
        "username": "bentleyw",
        "isMale": true,
        "phone": "068-620-269",
        "nid": "09293076518"
    }
]
}
            
GET /people?fields=[firstName, lastName]
{
"people": [
    {
        "firstName": "Kennedy",
        "lastName": "Cote"
    },
    {
        "firstName": "Jocelyn",
        "lastName": "Parsons"
    },
    {
        "firstName": "Mia",
        "lastName": "Key"
    },
    {
        "firstName": "Jackson",
        "lastName": "Whitney"
    },
    {
        "firstName": "Nevaeh",
        "lastName": "Torres"
    },
    {
        "firstName": "Samuel",
        "lastName": "Best"
    },
    {
        "firstName": "Ellie",
        "lastName": "Hooper"
    },
    {
        "firstName": "Kevin",
        "lastName": "White"
    },
    {
        "firstName": "Xavier",
        "lastName": "Wooten"
    },
    {
        "firstName": "Naomi",
        "lastName": "Chan"
    },
    {
        "firstName": "Owen",
        "lastName": "Ramsey"
    },
    {
        "firstName": "Victoria",
        "lastName": "Hunter"
    },
    {
        "firstName": "Harper",
        "lastName": "Boone"
    },
    {
        "firstName": "Nicholas",
        "lastName": "Mcknight"
    },
    {
        "firstName": "Kaylee",
        "lastName": "Stone"
    },
    {
        "firstName": "Hudson",
        "lastName": "Schultz"
    },
    {
        "firstName": "Thomas",
        "lastName": "Copeland"
    },
    {
        "firstName": "Madeline",
        "lastName": "Odom"
    },
    {
        "firstName": "Colton",
        "lastName": "Humphrey"
    },
    {
        "firstName": "Alexis",
        "lastName": "Chandler"
    }
]
}
            

XML vs JSON

PLAIN 171120 bytes
GZIP  30855  bytes
<people>
  <person>
    <firstname>Grayson</firstname>
    <lastname>Wilkins</lastname>
    <username>graysonw</username>
    <ismale>true</ismale>
    <phone>197-391-9087</phone>
    <nid>39113050916</nid>
  </person>
  <person>
    <firstname>Nathaniel</firstname>
    <lastname>Page</lastname>
    <username>npage</username>
    <ismale>true</ismale>
    <phone>922-083-8066</phone>
    <nid>87061196536</nid>
  </person>
  <person>
    <firstname>Annabelle</firstname>
    <lastname>Parks</lastname>
    <username>aparks</username>
    <ismale>false</ismale>
    <phone>860-706-550</phone>
    <nid>10221048164</nid>
  </person>
  <person>
    <firstname>Carlos</firstname>
    <lastname>Robbins</lastname>
    <username>crobbins</username>
    <ismale>true</ismale>
    <phone>633-199-3647</phone>
    <nid>85022650976</nid>
  </person>
  <person>
    <firstname>Sebastian</firstname>
    <lastname>Mckenzie</lastname>
    <username>sebastianm</username>
    <ismale>true</ismale>
    <phone>031-821-2542</phone>
    <nid>08221481578</nid>
  </person>
  <person>
    <firstname>Aiden</firstname>
    <lastname>Mercado</lastname>
    <username>aidenm</username>
    <ismale>true</ismale>
    <phone>413-006-9553</phone>
    <nid>01302174138</nid>
  </person>
  <person>
    <firstname>Jacob</firstname>
    <lastname>Terry</lastname>
    <username>jacobt</username>
    <ismale>true</ismale>
    <phone>674-749-7019</phone>
    <nid>09312515478</nid>
  </person>
  <person>
    <firstname>Hannah</firstname>
    <lastname>Hart</lastname>
    <username>hhart</username>
    <ismale>false</ismale>
    <phone>489-395-5946</phone>
    <nid>62042618284</nid>
  </person>
  <person>
    <firstname>Jaxon</firstname>
    <lastname>Wagner</lastname>
    <username>jaxonw</username>
    <ismale>true</ismale>
    <phone>500-770-244</phone>
    <nid>71081634910</nid>
  </person>
  <person>
    <firstname>Chloe</firstname>
    <lastname>Sanders</lastname>
    <username>csanders</username>
    <ismale>false</ismale>
    <phone>714-971-5327</phone>
    <nid>12302121384</nid>
  </person>
  <person>
    <firstname>Christopher</firstname>
    <lastname>Brennan</lastname>
    <username>christopherb</username>
    <ismale>true</ismale>
    <phone>272-938-005</phone>
    <nid>06253116718</nid>
  </person>
  <person>
    <firstname>Lydia</firstname>
    <lastname>Montoya</lastname>
    <username>lmontoya</username>
    <ismale>false</ismale>
    <phone>106-629-312</phone>
    <nid>13221541804</nid>
  </person>
  <person>
    <firstname>Adrian</firstname>
    <lastname>Carver</lastname>
    <username>acarver</username>
    <ismale>true</ismale>
    <phone>355-762-7781</phone>
    <nid>97111290392</nid>
  </person>
  <person>
    <firstname>Samuel</firstname>
    <lastname>Mccullough</lastname>
    <username>samuelm</username>
    <ismale>true</ismale>
    <phone>382-502-589</phone>
    <nid>66123176094</nid>
  </person>
  <person>
    <firstname>Landon</firstname>
    <lastname>Grimes</lastname>
    <username>lgrimes</username>
    <ismale>true</ismale>
    <phone>082-649-575</phone>
    <nid>10272237014</nid>
  </person>
  <person>
    <firstname>Isabella</firstname>
    <lastname>Merrill</lastname>
    <username>isabellam</username>
    <ismale>false</ismale>
    <phone>795-762-4257</phone>
    <nid>12292864824</nid>
  </person>
  <person>
    <firstname>Landon</firstname>
    <lastname>Dale</lastname>
    <username>ldale</username>
    <ismale>true</ismale>
    <phone>959-023-135</phone>
    <nid>41081330132</nid>
  </person>
  <person>
    <firstname>Madeline</firstname>
    <lastname>Spence</lastname>
    <username>mspence</username>
    <ismale>false</ismale>
    <phone>116-600-1377</phone>
    <nid>54070686488</nid>
  </person>
  <person>
    <firstname>Damian</firstname>
    <lastname>Henderson</lastname>
    <username>damianh</username>
    <ismale>true</ismale>
    <phone>091-797-810</phone>
    <nid>37091971136</nid>
  </person>
  <person>
    <firstname>Dominic</firstname>
    <lastname>Hurley</lastname>
    <username>dominich</username>
    <ismale>true</ismale>
    <phone>842-593-567</phone>
    <nid>12281078514</nid>
  </person>
  <person>
    <firstname>Mackenzie</firstname>
    <lastname>Cooper</lastname>
    <username>mcooper</username>
    <ismale>false</ismale>
    <phone>335-422-488</phone>
    <nid>12292859744</nid>
  </person>
  <person>
    <firstname>Carson</firstname>
    <lastname>Barnes</lastname>
    <username>cbarnes</username>
    <ismale>true</ismale>
    <phone>649-653-776</phone>
    <nid>13231549814</nid>
  </person>
  <person>
    <firstname>Elizabeth</firstname>
    <lastname>Sutton</lastname>
    <username>esutton</username>
    <ismale>false</ismale>
    <phone>205-975-232</phone>
    <nid>86070205666</nid>
  </person>
</people>
            
PLAIN 121115 bytes (70%)
GZIP  29553  bytes (96%)
{
"people": [
    {
        "firstName": "Jason",
        "lastName": "Page",
        "username": "jasonp",
        "isMale": true,
        "phone": "142-808-3743",
        "nid": "12252671714"
    },
    {
        "firstName": "Nathaniel",
        "lastName": "Chaney",
        "username": "nchaney",
        "isMale": true,
        "phone": "481-659-731",
        "nid": "55122958778"
    },
    {
        "firstName": "Alexis",
        "lastName": "Christensen",
        "username": "alexisc",
        "isMale": false,
        "phone": "802-046-8689",
        "nid": "13240667404"
    },
    {
        "firstName": "Sophie",
        "lastName": "Duke",
        "username": "sophied",
        "isMale": false,
        "phone": "316-701-3331",
        "nid": "96071009282"
    },
    {
        "firstName": "Aria",
        "lastName": "Wolf",
        "username": "ariaw",
        "isMale": false,
        "phone": "682-172-2915",
        "nid": "12231830224"
    },
    {
        "firstName": "Sebastian",
        "lastName": "Hogan",
        "username": "sebastianh",
        "isMale": true,
        "phone": "096-033-8930",
        "nid": "65082565494"
    },
    {
        "firstName": "Austin",
        "lastName": "Rice",
        "username": "austinr",
        "isMale": true,
        "phone": "558-871-007",
        "nid": "91102544132"
    },
    {
        "firstName": "Elijah",
        "lastName": "Savage",
        "username": "esavage",
        "isMale": true,
        "phone": "009-900-8985",
        "nid": "10301742394"
    },
    {
        "firstName": "Colton",
        "lastName": "Morris",
        "username": "cmorris",
        "isMale": true,
        "phone": "029-957-8223",
        "nid": "84062095076"
    },
    {
        "firstName": "Juan",
        "lastName": "Bass",
        "username": "jbass",
        "isMale": true,
        "phone": "701-810-3457",
        "nid": "10311068954"
    },
    {
        "firstName": "Brody",
        "lastName": "Reese",
        "username": "breese",
        "isMale": true,
        "phone": "320-610-636",
        "nid": "82112959596"
    },
    {
        "firstName": "Stella",
        "lastName": "Bernard",
        "username": "sbernard",
        "isMale": false,
        "phone": "134-462-1924",
        "nid": "03272935528"
    },
    {
        "firstName": "Isaac",
        "lastName": "Webb",
        "username": "iwebb",
        "isMale": true,
        "phone": "136-000-422",
        "nid": "10231649814"
    },
    {
        "firstName": "Jackson",
        "lastName": "Bryan",
        "username": "jacksonb",
        "isMale": true,
        "phone": "629-900-7729",
        "nid": "83071189716"
    },
    {
        "firstName": "Liam",
        "lastName": "Beard",
        "username": "lbeard",
        "isMale": true,
        "phone": "327-639-010",
        "nid": "86050552116"
    },
    {
        "firstName": "Parker",
        "lastName": "Duffy",
        "username": "pduffy",
        "isMale": true,
        "phone": "484-829-6473",
        "nid": "56122687578"
    },
    {
        "firstName": "Lucy",
        "lastName": "Moody",
        "username": "lucym",
        "isMale": false,
        "phone": "723-670-5321",
        "nid": "77051444320"
    },
    {
        "firstName": "Aaliyah",
        "lastName": "Leonard",
        "username": "aleonard",
        "isMale": false,
        "phone": "005-293-048",
        "nid": "08281195048"
    },
    {
        "firstName": "Brody",
        "lastName": "Norris",
        "username": "brodyn",
        "isMale": true,
        "phone": "789-653-2217",
        "nid": "58050880078"
    },
    {
        "firstName": "Juan",
        "lastName": "Valenzuela",
        "username": "juanv",
        "isMale": true,
        "phone": "060-065-140",
        "nid": "93052612612"
    },
    {
        "firstName": "Jocelyn",
        "lastName": "Collins",
        "username": "jcollins",
        "isMale": false,
        "phone": "010-257-964",
        "nid": "65083166064"
    },
    {
        "firstName": "Bentley",
        "lastName": "Welch",
        "username": "bentleyw",
        "isMale": true,
        "phone": "068-620-269",
        "nid": "09293076518"
    }
]
}
            

Toolbox

Client code - iOS

// PersonDataDownloader.m
#import "PersonDataDownloader_M.h"
#import "Person.h"
#import "TaskCallsSynchronizer.h"


@interface PersonDataDownloader_M ()
@property(nonatomic, strong) TaskCallsSynchronizer *synchronizer;
@end

@implementation PersonDataDownloader_M

- (void)downloadDataForPerson:(Person *)person {
    self.synchronizer = [TaskCallsSynchronizer new];
    self.synchronizer.completionHandler = ^(NSArray *taskDataObjects, NSArray *taskResponses) {
        // Merge downloaded data here and perform the rest...
    };

    self.synchronizer.errorHandler = ^(NSArray *errors) {
        NSLog(@"All calls went bad...");
    };

    NSURLSessionDataTask *personalInfoTask = [self personalInfoTaskForPerson:person synchronizer:self.synchronizer];
    NSURLSessionDataTask *accountNumberTask = [self accountNumberTaskForPerson:person synchronizer:self.synchronizer];

    [self.synchronizer addTask:personalInfoTask withName:@"InfoTask"];
    [self.synchronizer addTask:accountNumberTask withName:@"AccountTask"];

    [personalInfoTask resume];
    [accountNumberTask resume];
}

#pragma mark - Tasks

- (NSURLSessionDataTask *)personalInfoTaskForPerson:(Person *)person synchronizer:(TaskCallsSynchronizer *)synchronizer {
    NSURLSession *session = [NSURLSession sharedSession];
    __typeof(synchronizer) __weak weakSynchronizer = synchronizer;
    NSURLSessionDataTask *personalInfoTask = [session dataTaskWithURL:[self urlForPersonsPersonalInfo:person]
                                                    completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                NSString *taskName = @"InfoTask";
                if (data && !error) {
                    [weakSynchronizer completeTaskWithName:taskName data:data response:response];
                } else {
                    [weakSynchronizer errorDidOccur:error forTaskWithName:taskName];
                }
            }];
    return personalInfoTask;
}

- (NSURLSessionDataTask *)accountNumberTaskForPerson:(Person *)person synchronizer:(TaskCallsSynchronizer *)synchronizer {
    NSURLSession *session = [NSURLSession sharedSession];
    __typeof(synchronizer) __weak weakSynchronizer = synchronizer;
    NSURLSessionDataTask *accountNumberTask = [session dataTaskWithURL:[self urlForPersonsAccountNumber:person]
                                                     completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                NSString *taskName = @"AccountTask";
                if (data && !error) {
                    [weakSynchronizer completeTaskWithName:taskName data:data response:response];
                } else {
                    [weakSynchronizer errorDidOccur:error forTaskWithName:taskName];
                }
            }];
    return accountNumberTask;
}

#pragma mark - URLs

- (NSURL *)urlForPersonsPersonalInfo:(Person *)person {
    NSString *urlString = [NSString stringWithFormat:@"http://e1.com/persons/%ld", (long)person.id];
    return [[NSURL alloc] initWithString:urlString];
}

- (NSURL *)urlForPersonsAccountNumber:(Person *)person {
    NSString *urlString = [NSString stringWithFormat:@"http://e2.com/accounts/%ld", (long)person.id];
    return [[NSURL alloc] initWithString:urlString];
}

@end



// TaskCallSynchronizer.m
#import "TaskCallsSynchronizer.h"


@interface TaskCallsSynchronizer ()
@property(nonatomic, readonly) NSMapTable *tasksMap;
@property(nonatomic, readonly) NSMutableArray *dataObjects;
@property(nonatomic, readonly) NSMutableArray *responses;
@property(nonatomic, readonly) NSMutableArray *errors;
@property(nonatomic) BOOL errorEncountered;
@end

@implementation TaskCallsSynchronizer

- (id)init {
    self = [super init];
    if (self) {
        _tasksMap = [NSMapTable strongToWeakObjectsMapTable];
        _dataObjects = [NSMutableArray array];
        _responses = [NSMutableArray array];
        _errors = [NSMutableArray array];
    }

    return self;
}

- (void)addTask:(NSURLSessionDataTask *)task withName:(NSString *)name {
    [self.tasksMap setObject:task forKey:name];
}

- (void)completeTaskWithName:(NSString *)name data:(NSData *)data response:(NSURLResponse *)response {
    @synchronized (self) {
        NSURLSessionDataTask *task = [self.tasksMap objectForKey:name];
        BOOL isDownloadStateValid = task.state != NSURLSessionTaskStateCompleted && !self.errorEncountered;
        if (isDownloadStateValid) {
            NSLog(@"Ups, something's wrong.");
        } else {
            [self.dataObjects addObject:data];
            [self.responses addObject:response];
            [self.tasksMap removeObjectForKey:name];

            if ([self.tasksMap count] == 0 && self.completionHandler) {
                self.completionHandler(self.dataObjects, self.responses);
            }
        }
    }
}

- (void)errorDidOccur:(NSError *)error forTaskWithName:(NSString *)name {
    @synchronized (self) {
        self.errorEncountered = YES;
        [self.errors addObject:error];
        [self.tasksMap removeObjectForKey:name];

        if ([self.tasksMap count] == 0 && self.errorHandler) {
            self.errorHandler(self.errors);
        }
    }
}

@end


        

Client code - Android

protected void onResume() {
        super.onResume();
        try {
            download();
        } catch (Exception e) {
            Log.e(TAG, "Error while downloading data", e);
        }
}

public void download() throws ExecutionException, InterruptedException {
        Future<Response<JsonObject>> personFuture = Ion.
                with(this).
                load("http://e1.com/persons/123").
                asJsonObject().withResponse();

        Future<Response<JsonObject>> accountFuture = Ion.
                with(this).
                load("http://e2.com/accounts/456").
                asJsonObject().
                withResponse();

        Response<JsonObject> personResponse = personFuture.get();
        Response<JsonObject> accountResponse = accountFuture.get();

        if (personResponse.getException() != null && accountResponse.getException() != null) {

            JsonObject person = personResponse.getResult().getAsJsonObject();
            JsonObject account = personResponse.getResult().getAsJsonObject();

            String firstName = person.getAsJsonPrimitive("firstName").toString();
            String lastName = person.getAsJsonPrimitive("lastName").toString();
            String accountNo = account.getAsJsonPrimitive("accountNo").toString();

        } else {
            Log.e(TAG, "Error while downloading account", accountResponse.getException());
            Log.e(TAG, "Error while downloading person", personResponse.getException());
        }
}

Automated testing sucks!

Updating app takes long

server side version

def parts = [
    "http://e1.com/persons/123": ['firstName', 'lastName'],
    "http://e2.com/accounts/456": ['accountNo'],
]

def output = withPool(2) {
  return parts.collectParallel({ url, fields ->

    def json = new JsonSlurper().parse(url as URL)

    return fields.collect ({ field ->
      ["$field": json[field]]
    })

  }).flatten().sum()
}

Nouns vs Verbs

POST /following
{
  "from": 123,
  "to": 456
}
POST /users/456/follow
 

Common problems

versioning

  • http://api.example.com/v1
  • application/vnd.example.com.v1+json
  • http://v1.api.example.com

http cache

Expires: Sat, 26 Jul 1997 05:00:00 GMT
  • iOS (AFNetworking + NSURLCache)
  • Android (Retrofit + OkHttp + HttpResponseCache)

paging

GET /users?page=:page_number

Arthur Bob Celine Daniel Eve Fred George

Page size 3

Page 1: Arthur Bob Celine

Delete: Bob

Page 1: Arthur Bobby Daniel

Page 2: Eve Fred George

relative page ref

{
  // ...
  "nextPage": "/users?since=5476238046501"
}

Case study - utest app

  • In the wild testing service
  • Testers located all over the world
  • Legacy API

Reductions

  • 36 to 20 endpoints
  • 86 to 20 calls (visiting all screens once)
  • 96% data size reduction (84% without GZIP)

Scheme

q&a