How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
Why HATEOAS
1. WHY
HATEOAS
A simple case study on the often ignored REST constraint
Wayne Lee, June 2009
http://trilancer.wordpress.com/
2. 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
3. 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
4. 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
5. A Simple Case Study
Imagine You’re Building
an Online Order Manager
6. Where You Start
Users Table Orders Table
ID Name ID User_ID
1 Tom 123 1
2 Jerry 456 2
… … … …
Server 1: myorders.com
• One server
• Two DB tables
• Free user registration
7. 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
8. Then lots client apps emerge …
• From users, fans, solution providers,
mashup makers…
• 10s 100s 1000s …
• Based on the simple “REST” API V1
9. After some time …
• Some suggest implicit user id in session /
cookie NOT a good idea …
• That user_name should be included in URI
10. “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
11. 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
12. 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
13. DB Changes
Users Table Orders Table for Free Users
ID Name Type ID User_ID
1 Tom Free 123 1
2 Jerry Pro … …
… … …
Server 1: myorders.com
Orders Table for Pro Users
ID User_ID
456 2
… …
Server 2: pro.myorders.com
14. “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
15. 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
16. 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
17. DB Changes
Users Table Orders Table for Free Users
ID Name Type Domain ID User_ID
1 Tom Free N/A 123 1
2 Jerry Pro N/A … …
3 Susan VIP susan_test
Server 1: myorders.com
Orders Table for Pro Users Orders Tables for VIP User
ID User_ID ID data
456 2 789 …
… … … …
Server 2: pro.myorders.com Server 3: susan_test.myorders.com
Server 4: mikeabc.myorders.com
Server 5: Alf_shop.myorders.com
Server 6: anna_box.myorders.com
18. “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 …
19. 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 …
20. Things Can Get Even More
Complicated
More requirements, more offerings,
more functions, more features, more
rules, clusters, load-balancers, data
partitions, backups …
21. So Will Servlet Logic …
And maintenance, logging, testing,
trouble-shooting …
24. “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
25. 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
26. Same API Works
across Various Account Types …
Free Pro VIP
POST /login POST /login POST /login
User_Data: { User_Data: { User_Data: {
name: “tom”, name: “jerry”, name: “susan”,
order_list_uri: order_list_uri: order_list_uri:
“/tom/orders” “pro.myorders.com/jerry/orders” “susan_test.myorders.com/orders”
} } }
GET /tom/orders GET pro.myorders.com/jerry/orders GET
susan_test.myorders.com/orders
Order_List = [ Order_List = [ Order_List = [
order: { order: { order: {
id:123, id:456, id:789,
uri:“/tom/orders/123” uri:“suasan_test.myorders.com/orders/78
} uri:“pro.myorders.com/jerry/orders/456”} 9”}
] ] ]
GET /tom/orders/123 GET GET
pro.myorders.com/jerry/orders/456 suasan_test.myorders.com/orders/7
89
27. … 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’ }, Data from different
DBs mixed
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’ }
]
29. Different Zones for Different Parkers
Free parkers
Pro parkers
VIP parker VIP parker VIP parker VIP parker
VIP parker VIP parker VIP parker VIP parker
Gate
30. A once-free-now-VIP Parker who
cannot get rid of old habits …
“Sir, your lot is in the VIP zone
around the corner…”
Pro parkers “!!!!...”
“Since you’re VIP customer, we’ll
redirect your car there for free …”
Free parkers
VIP parker VIP parker VIP parker VIP parker
VIP parker VIP parker VIP parker VIP parker
Gate
31. “REST” API without HATEOAS
Be prepared to repeat this mess each and every day
Pro parkers
Free parkers
VIP parker VIP parker VIP parker VIP parker
VIP parker VIP parker VIP parker VIP parker
Gate
32. A HATEOAS API Scenario
Instructions to each customer each time
Free parkers
Pro parkers
VIP parker VIP parker VIP parker VIP parker
VIP parker VIP parker VIP parker UNDER CONSTRUCTION
“Sir, your lot is being repaired,
fortunately we’ve allocated a new
one for you, here’s the route …”
“I see, thanks a lot ”
Gate
33. RPC vs. HATEOAS
Not necessarily future-proof but more efficient for now
Free Gate
Pro Gate
Free parkers
Pro parkers
VIP parker VIP parker VIP parker VIP parker
VIP parker VIP parker VIP parker VIP parker
VIP Gate Old Gate Blocked
Old client:
“!@#$$#%^&^%!!!!”
34. 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