Why HATEOAS

Post on 01-Nov-2014

24891 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

A simple case study around HATEOAS (Hypermedia as the engine of application state), an essential constraint of the REST architecture style

Transcript

WHY HATEOAS

A simple case study on the often ignored REST constraint

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

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

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

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

A Simple Case Study

Imagine You’re Building an Online Order Manager

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

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

Then lots client apps emerge …

From users, fans, solution providers, mashup

makers…

10s 100s 1000s …

Based on the simple “REST”

API V1

After some time …

Some suggest implicit

user id in session / cookie NOT

a good idea …

That user_name

should be included in URI

“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

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

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

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

“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

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

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

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

“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 …

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 …

Things Can Get Even More Complicated

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

partitions, backups …

So Will Servlet

Logic …

And maintenance, logging, testing, trouble-shooting …

And Client App Implementation Cost

What’s Wrong in the First Place?

So

“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

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

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

… 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

Put It Visually

Imagine a Parking Lot

Different Zones for Different Parkers

Free parkersPro parkers

VIP parkerVIP parkerVIP parker

Gate

VIP parkerVIP parkerVIP parker

VIP parker

VIP parker

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

…”

“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

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

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:“!@#$$#%^&^%!!!!”

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

top related