Why HATEOAS

30,029
-1

Published on

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

Published in: Technology, Business
5 Comments
72 Likes
Statistics
Notes
No Downloads
Views
Total Views
30,029
On Slideshare
0
From Embeds
0
Number of Embeds
56
Actions
Shares
0
Downloads
404
Comments
5
Likes
72
Embeds 0
No embeds

No notes for slide

Why HATEOAS

  1. 1. WHY HATEOAS A simple case study on the often ignored REST constraint Wayne Lee, June 2009 http://trilancer.wordpress.com/
  2. 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. 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. 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. 5. A Simple Case Study Imagine You’re Building an Online Order Manager
  6. 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. 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. 8. Then lots client apps emerge … • From users, fans, solution providers, mashup makers… • 10s  100s  1000s … • Based on the simple “REST” API V1
  9. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 20. Things Can Get Even More Complicated More requirements, more offerings, more functions, more features, more rules, clusters, load-balancers, data partitions, backups …
  21. 21. So Will Servlet Logic … And maintenance, logging, testing, trouble-shooting …
  22. 22. And Client App Implementation Cost
  23. 23. So What’s Wrong in the First Place?
  24. 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. 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. 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. 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’ } ]
  28. 28. Put It Visually Imagine a Parking Lot
  29. 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. 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. 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. 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. 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. 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
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×