Extend your REST API

Post on 15-Apr-2017

101 views 0 download

transcript

One API to serve them allfor free. no SMS, no registration

Alexey Tokar

Head of Development @ WorldApp

What this talk is not about

2

HTTP method semantics

GET - for listPOST - for create

PATCH - for partial updateDELETE - for delete

PUT - for update3

Resources name convention

/programmer/42VS

/programmers/424

HTTP status code meaning

1xx Informational2xx Success3xx Redirection4xx Client Error5xx Server Error

5

HATEoAS architecture

Hypermedia

as the

Engine

of

Application

State

6

What we are going to coverDifferent devices and environmentsApplication and API designReal behavior of services and our expectations

7

What our goals are?Blazing fast processing and respondingAwesome stability and expected behaviorLimitless scalability Tremendous flexibility in operation and support

8

Two major API design

9

N device types : N APIs

10

Pros & consEasy to manageOptimized for deviceSimple codebase

11

Exponential diversityHard to deployLong way to fix

N device types : 1 API

12

Pros & consEasy to manageOptimized for deviceSimple codebaseEasy to deployShort way to fixLinear diversity :)

13

Reduced flexibility

One API to serve them all

14

We don’t need that sheet!

15

Please welcome our resources!GET /posts/1{

id : "/posts/1",

title : "title",

created: "2016-10-16T10:00:00Z",

author : {

href: "/users/2"

},

similar: {

href: "/posts/1/similar"

}

}

GET /users/2{

id : "/users/2",

name: "alexey",

dob: "1986-08-14"

}

16Looks like HATEoAS

We need only part of your Data

17

Did you know...1 Twitter post thought REST API takes about 5KiBTransferring 20KiB over 3G will last approximately

20ms

Average type speed is 120wpm for keyboards and 30wpm for mobile devices* (100ms and 400ms per character)

Even level 1 gzip could save up to x15 of your REST API traffic 18

* http://www.pocketables.com/archives/mobile-device-keyboard

?fields=<fieldsList>GET /posts/1?fields=title,id{

id : "/posts/1",

title : "title"

}

19http://drawingimage.com/files/1/Llama-Realistic-Drawing.jpg

Ordinary Service could look like

@Serviceclass UserService { public User find( String id, Set<Field> fields ) { //... }}

20

LimitationsViolation of the IoC principleHard to manage consistency of new servicesNeed to rewrite all the codebase

21

Possible data path

22

Easy to manage solutionList<String> fields = Arrays.asList( "id", "title" );

ObjectMapper mapper = new ObjectMapper();JsonNode tree = mapper.readTree( response );

Iterator<Map.Entry<String, JsonNode>> it = tree.fields();while ( it.hasNext() ) { Map.Entry<String, JsonNode> entry = it.next(); String key = entry.getKey(); if ( !fields.contains( key ) ) { it.remove(); }}

23

Give us all we need

24

Different data on a page

25

Optimal solutions

26

SPDY

HTTP/2With multiplexing

HTTP 1.1With keep-alive

How Big Guys do it?POST /batch HTTP/1.1Authorization: Bearer your_auth_tokenHost: www.googleapis.comContent-Type: multipart/mixed; boundary=batch_foobarbazContent-Length: total_content_length

--batch_foobarbazContent-Type: application/httpGET /farm/v1/animals/pony

--batch_foobarbazContent-Type: application/httpGET /farm/v1/animals--batch_foobarbaz-- 27

Semantically correctvs

Hard to use

28

/batchPOST /batch{

myProfile : "/users/me",

post : "/posts/1?fields=id,author,href"

}

{

myProfile : {

id : "/users/2",

name: "alexey",

dob: "1986-08-14"

},

post : {

id : "/posts/1",

author : {

href: "/users/2"

}

}

}29

Simple data proxyObjectNode tree = mapper.createObjectNode();

for ( Map.Entry<String, String> nameToUrl : map.entrySet() ) { tree.set( nameToUrl.getKey(), mapper.readTree( new URL( request.getScheme(), request.getServerName(), request.getServerPort(), nameToUrl.getValue() ) ) );}

30

Please, be so kind to include relations

31

Data related to main content

32

?include=<subObject>GET /posts/1?include=author{

id : "/posts/1",

title : "title",

created: "2016-10-16T10:00:00Z",

author : {

id : "/users/2",

name: "alexey",

dob: "1986-08-14"

},

similar: {

href: "/posts/1/similar"

}

} 33

GET /posts/1{

id : "/posts/1",

title : "title",

created: "2016-10-16T10:00:00Z",

author : {

href: "/users/2"

},

similar: {

href: "/posts/1/similar"

}

}

Altogether!POST /batch{

part1 : “/resource/24

? include=some,sub,resource

& fields=only,what,you,need”

}

34

Why it’s a good solution?Only one set of resourcesAsynchronization on the backendOnly one HTTP request per screen with only needed

dataScalable and manageable solutionSingle entrance and exit pointSimple services, dao and dtoCould be adapted to any* existing API

35

Right questionsWhy you are not using Protobuf or Flatbuffers?

Good json serialization library + gzip + etag negate the difference. Also JSON is still the most popular data format for APIs.

You are talking about performance but still make heavy DB queries.

Yes, but this is a tradeoff for simplification and ease of support. We still could serve 10K+ rps per node.

Why internal dispatching is more preferable than custom monster-join approach?

It is simpler to add more servers for our services and tweak performance that way instead of trying to fix each complex-query performance. But this is another discussable tradeoff :)

36

37

https://github.com/alexeytokar/rainbow-rest

Questions?Alexey Tokar

Head of Development @ WorldApp

38