Top Banner
WHY HATEOAS A simple case study on the often ignored REST constraint Wayne Lee, June 2009 http://trilancer.wordpress.com/
34

Why HATEOAS

Nov 01, 2014

Download

Technology

Lee Wayne

A simple case study around HATEOAS (Hypermedia as the engine of application state), an essential constraint of the REST architecture style
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Why HATEOAS

WHY HATEOAS

A simple case study on the often ignored REST constraint

Wayne Lee, June 2009http://trilancer.wordpress.com/

Page 2: Why HATEOAS

Background•

Representational state transfer (REST)–

A software architecture style

for distributed

hypermedia

systems, e.g., the World Wide Web–

Introduced by Roy Fielding

in 2000

REST Constraints–

Identification of resources

Manipulation of resources through representations–

Self-descriptive messages

Hypermedia as the engine of application state

Page 3: Why HATEOAS

HATEOAS Hypermedia as the engine of application state

The model of application is an engine

that moves from one state

to another by

picking alternative state transitions in current set of representations

Or simply put:–

State: where the user is

i.e., current resources

Transitions: instructions on user’s next steps•

i.e., links / forms from current resources to others

Presenter
Presentation Notes
http://www.infoq.com/articles/mark-baker-hypermedia
Page 4: Why HATEOAS

Why HATEOAS•

Resources will evolve

over time

Naming, URI, location, partition …

Thus any assumptions

about server resources will break

eventually

URI pattern, valid state transitions …

HATEOAS is about reducing

assumptions–

Loose coupling between client & server

Allowing each to evolve independently

Page 5: Why HATEOAS

A Simple Case Study

Imagine You’re Building an Online Order Manager

Page 6: Why HATEOAS

Where You Start

ID Name

1 Tom

2 Jerry

… …

ID User_ID

123 1

456 2

… …

Users Table Orders Table

One

server

Two

DB tables

Free

user registration

Server 1: myorders.com

Page 7: Why HATEOAS

Your “REST” API V1•

Step 1: POST /login (user_name, password)

Session created•

Step 2: GET /orders

Get Order List, with user id implicitly

provided in session•

Step 3: GET /orders/{order_id}

Get specific Order data through cooked URI

Sample order list data in JSON format:[

order: {id:123},order: {id:456},

]•

URI cooking rules:

List_URI

= ‘/orders’–

Order_URI

= List_URI

+ order_list[n].order.id

Seems really simple for client app implementation, for now

Page 8: Why HATEOAS

Then lots client apps emerge …

From users, fans, solution providers, mashup

makers…

10s 100s 1000s …

Based on the simple “REST”

API V1

Page 9: Why HATEOAS

After some time …

Some suggest implicit

user id in session / cookie NOT

a good idea …

That user_name

should be included in URI

Page 10: Why HATEOAS

“REST” API V1.1•

Step 1: POST /login (user_name, password)–

Session created

Step 2: GET /{user_name}/orders–

Get Order List, with user name explicitly

provided

Step 3: GET /{user_name}/orders/{order_id}–

Get specific Order

data through cooked URI

URI cooking rules:–

User_name

retrieved from client local input

List_URI

= “/”

+ user_name

+ “/orders”–

Order_URI

= List_URI

+ order_list[n].order.id

Seems simple for client implementation still

Page 11: Why HATEOAS

But, what about old apps?

Just let them break? –

Not Acceptable

Make sure Orders

servlet

maintain backward compatibility:–

Retrieve user_name

from request URI

If NOT provided, retrieve from session data instead

In the end, API V1 still works for old apps

Page 12: Why HATEOAS

Then after some time …

You decide to add a paid

offerings:–

Free

accounts:

Data on the old host–

Professional accounts:

Data moved to a new faster server•

With a new domain name

Page 13: Why HATEOAS

Server 1: myorders.com

DB Changes

ID Name Type

1 Tom Free

2 Jerry Pro

… … …

ID User_ID

123 1

… …

Users Table Orders Table for Free Users

ID User_ID

456 2

… …

Orders Table for Pro Users

Server 2: pro.myorders.com

Page 14: Why HATEOAS

“REST” API V2•

Step 1: POST /login (user_name, password)–

Session created, with User_Type

returned

Step 2:–

Free accounts:

GET /{user_name}/orders–

Pro accounts:

GET pro.myorders.com/{user_name}/orders

Step 3:–

Free accounts:

GET /{user_name}/orders/{order_id}–

Pro accounts:

GET pro.myorders.com/{user_name}/orders/{order_id}

URI cooking rules:–

User_name

retrieved from client-side input–

User_type

received from server, “free”

or “pro”–

List_URI

= ((user_type

== ‘pro’) ? ‘pro.myorders.com/’

: ‘/’) + user_name

+ ‘/orders’–

Order_URI

= List_URI

+ order_list[n].order.id

Still ok for client implementation, nonetheless

Page 15: Why HATEOAS

Again, what about old apps?•

Just let them break? –

Still Not Acceptable

Modify Orders

servlet

logic again:–

Retrieve domain & user_name

from request URI

If NOT provided API V1.0•

Retrieve user_name

from session first, then

Lookup Users table to determine user_type, i.e., which DB to use–

If only user_name

provided API V1.1

Likewise, lookup Users table to determine which DB to use

In the end, API V1/V1.1 still works fine

Page 16: Why HATEOAS

As time goes by

You think it time for a VIP

offering:–

Free

accounts:

Data on the old host–

Professional accounts:

Data on a faster server•

With a new domain name

VIP accounts:•

Dedicated DB server

Custom domain name

Page 17: Why HATEOAS

Server 6: anna_box.myorders.comServer 5: Alf_shop.myorders.com

Server 4: mikeabc.myorders.comServer 3: susan_test.myorders.com

Server 1: myorders.com

DB Changes

ID Name Type Domain

1 Tom Free N/A

2 Jerry Pro N/A

3 Susan VIP susan_test

ID User_ID

123 1

… …

Users Table Orders Table for Free Users

ID User_ID

456 2

… …

Orders Table for Pro Users

Server 2: pro.myorders.com

ID data

789 …

… …

Orders Tables for VIP User

Page 18: Why HATEOAS

“REST” API V3•

Step 1: POST /login (user_name, password)–

Session created, with User_Type, User_Domain

returned

Step 2:–

Free accounts:

GET /{user_name}/orders–

Pro accounts:

GET pro.myorders.com/{user_name}/orders–

VIP accounts:

GET {user_domain}.myorders.com/orders

Step 3:–

Free accounts:

GET /{user_name}/orders/{order_id}–

Pro accounts:

GET pro.myorders.com/{user_name}orders/{order_id}–

VIP accounts:

GET {user_domain}.myorders.com/orders/{order_id}

URI cooking rules:–

User_name

retrieved from client-side input–

User_type

received from server, “free”

or “pro”

or “vip”–

User_domain

received from server, maybe null–

List_URI

= user_domain

? user_domain

+ ‘.myorders.com/orders’

: (user_type

== ‘pro’

? ‘pro.myorders.com/’

: ‘/’) + user_name

+ ‘/orders’–

Order_URI

= List_URI

+ order_list[n].order.id

Seems not that simple for client anymore …

Page 19: Why HATEOAS

Again, what about old apps?•

“We’ll support old client apps, as usual…”

Modify Orders

servlet

logic again:–

Retrieve domain & user_name

from request URI–

If domain name is “Pro”

API V2/V3•

Use DB on pro.myorders.com–

If domain name is not “Pro”

API V3•

Use DB on {domain_name}.myorders.com–

If NOTHING is provided API V1.0•

Retrieve user_name

from session first, then•

Then lookup Users table to get user_type, user_domain–

If user_type

is “Free”, use DB on myorders.com–

If user_type

is “Pro”, use DB on pro.myorders.com–

If user_type

is “VIP”, use DB on {user_domain}.myorders.com–

If only user_name

is provided API V1.1•

Likewise, lookup Users table to determine which DB to use

In the end, API V1/V1.1/V2 still works fine, sadly …

Page 20: Why HATEOAS

Things Can Get Even More Complicated

More requirements, more offerings, more functions, more features, more rules, clusters, load-balancers, data

partitions, backups …

Page 21: Why HATEOAS

So Will Servlet

Logic …

And maintenance, logging, testing, trouble-shooting …

Page 22: Why HATEOAS

And Client App Implementation Cost

Page 23: Why HATEOAS

What’s Wrong in the First Place?

So

Page 24: Why HATEOAS

“REST” API V1 •

Step 1: POST /login (user_name, password)–

Session created

Step 2: GET /orders–

Get Order List, with user id implicitly

provided in session

Should NOT let client assume the URI, if potential changes expected

Step 3: GET /orders/{order_id}–

Get specific Order data through cooked URI

Should NOT let client assume the URI pattern , if potential changes expected

More assumptions allowed = More tightly coupling•

Simple effort for one-time client implementation possibly huge, on-going & ever-increasing liability for the server

Page 25: Why HATEOAS

A True REST API V0.1 Instead•

Step 1: POST /login (user_name, password)–

Session created, user related resource descriptions returned

User_Data: { name: “tom”,

order_list_uri: “/tom/orders” }

Step 2: GET {User_Data.order_list_uri}–

Retrieve order list data, sample data:

Order_List

= [ order: {id:123, uri:“/tom/orders/123”}

… ]

Step 3: GET {Order_List[n].order.uri}–

Retrieve specific Order data through given URI

Page 26: Why HATEOAS

Same API Works across Various Account Types …

Free Pro VIPPOST /login POST /login POST /loginUser_Data: {

name: “tom”,

order_list_uri:

“/tom/orders”

}

User_Data: {

name: “jerry”,

order_list_uri:

“pro.myorders.com/jerry/orders”

}

User_Data: {

name: “susan”,

order_list_uri:

“susan_test.myorders.com/orders”

}

GET /tom/orders GET pro.myorders.com/jerry/orders GET susan_test.myorders.com/orders

Order_List

= [

order: {

id:123,uri:“/tom/orders/123”

}

]

Order_List

= [

order: {

id:456,

uri:“pro.myorders.com/jerry/orders/456”}

]

Order_List

= [

order: {

id:789, uri:“suasan_test.myorders.com/orders/78

9”}

]

GET /tom/orders/123 GET pro.myorders.com/jerry/orders/456

GET suasan_test.myorders.com/orders/7

89

Page 27: Why HATEOAS

… and Adaptable to Various Situations

Tom just upgrade from Free

account to Pro, with bulk data migration scheduled later …

And Tom can continue work across DBs

Order_List

= [order:{ id:123, uri: ‘/tom/orders/123’

},order:{ id:456, uri: ‘pro.myorders.com/tom/orders/456’

}]

Pro.myorders.com

is down for maintenance, and Pro_1 is up as backup …

And users will hardly notice the change

Order_List

= [order:{ id:123, uri: ‘pro_1.myorders.com/tom/orders/123’

}order:{ id:456, uri: ‘pro_1.myorders.com/tom/orders/456’

}]

Data from different DBs

mixed

Page 28: Why HATEOAS

Put It Visually

Imagine a Parking Lot

Page 29: Why HATEOAS

Different Zones for Different Parkers

Free parkersPro parkers

VIP parkerVIP parkerVIP parker

Gate

VIP parkerVIP parkerVIP parker

VIP parker

VIP parker

Page 30: Why HATEOAS

A once-free-now-VIP Parker who cannot get rid of old habits …

Free parkers

Pro parkers

VIP parkerVIP parkerVIP parker

Gate

VIP parkerVIP parkerVIP parker

VIP parker

VIP parker

“Sir, your lot is in the VIP zone around the corner…”“!!!!...”“Since you’re VIP customer, we’ll redirect

your car there for free

…”

Page 31: Why HATEOAS

“REST”

API without HATEOAS Be prepared to repeat this mess each and every day

Free parkers

Pro parkers

VIP parkerVIP parkerVIP parker

Gate

VIP parkerVIP parkerVIP parker

VIP parker

VIP parker

Page 32: Why HATEOAS

A HATEOAS API Scenario Instructions to each customer each time

Free parkersPro parkers

VIP parkerVIP parkerVIP parker

Gate

VIP parkerVIP parker

VIP parker

VIP parker

“Sir, your lot is being repaired, fortunately we’ve allocated a new one for you, here’s the route …”“I see, thanks a lot ”

UNDER CONSTRUCTION

Page 33: Why HATEOAS

RPC vs. HATEOAS Not necessarily future-proof but more efficient for now

Free parkersPro parkers

VIP parkerVIP parkerVIP parker

VIP Gate

VIP parkerVIP parkerVIP parker VIP parker

VIP parker

Pro

Gat

e

Free Gate

Old Gate Blocked

Old client:“!@#$$#%^&^%!!!!”

Page 34: Why HATEOAS

Take Away•

HATEOAS is essential, for–

APIs

as well as internal organization

of complex

systems that may evolve over time–

In order to minimize maintenance cost and support old client apps

However, mind that–

Loose coupling less efficiency

So, if you’re 100% sure something will never change, e.g., /login as login URL, just let

everyone assume

it forever