HATEOAS: REST API Design වල අලුත් කතාව - SC Guide

හැඳින්වීම: අපේ API වලට ජීවයක් දෙමුද?
මචන්ලා, කොහොමද? අද අපි කතා කරන්න යන්නේ ටිකක් “advanced” පැත්තකට වෙන්න REST API Design ගැන. ගොඩක් වෙලාවට අපි API හදද්දි focus කරන්නේ “functional” පැත්තට විතරයිනේ? ඒ කියන්නේ, data ගන්න, data දාන්න වගේ දේවල් වලට විතරයි. හැබැයි, හොඳ API එකක් කියන්නේ ඒකට එහා ගිය දෙයක්. හරියට වෙබ් සයිට් එකක් වගේ, client ට 'ඊළඟට මොනවද කරන්න පුළුවන්' කියලා API එකෙන්ම කියන්න පුළුවන් නම්? ඒක නියමයි නේද? අන්න ඒ වගේ දෙයක් තමයි අපි අද බලන්න යන්නේ – HATEOAS කියන සංකල්පය ගැන. මේක ටිකක් අලුත් කතාවක් වගේ දැනෙයි සමහරවිට, හැබැයි REST Architecture එකේ ‘truest form’ එකට යන්න නම් මේක ගොඩක් වැදගත්.
අපි හැමෝම REST API හදලා තියෙනවා, පාවිච්චි කරලා තියෙනවා. ඒකේ සරල බව, stateless nature එක නිසා ගොඩක් දෙනෙක් කැමතියි. හැබැයි, ගොඩක් REST API වල තියෙන පොදු ගැටලුවක් තමයි, ඒවා හුදෙක් ‘data endpoints’ විතරක් වීම. Client එකට මොන URL එකට යන්න ඕනෙද, මොන HTTP method එක පාවිච්චි කරන්න ඕනෙද කියලා කලින්ම දැනගෙන ඉන්න ඕනේ. මේක හරියට පොතක් කියවනවා වගේ. පොතේ ඊළඟට මොනවද තියෙන්නේ කියලා කියවගෙන යනකම් දන්නේ නැහැ. හැබැයි අපි අද කතා කරන HATEOAS කියන සංකල්පය මේකට හොඳ විසඳුමක් දෙනවා. මේකෙන් අපේ API වලට ‘application state’ එකක් දෙනවා, ඒ වගේම client එකට ‘discover’ කරන්න පුළුවන් හැකියාවක් දෙනවා.
API කියන්නේ මොනවද ඇත්තටම?
හරි, මුලින්ම ආයෙත් පොඩ්ඩක් මතක් කරගමු API කියන්නේ මොනවද කියලා. අපි දන්නවානේ, API (Application Programming Interface) එකක් කියන්නේ applications දෙකක් අතරේ 'කතාබහ' කරන්න පාවිච්චි කරන interface එකක් කියලා. REST (Representational State Transfer) කියන්නේ මේ API හදන්න පාවිච්චි කරන architectural style එකක්. මේකේ මූලික සංකල්ප කීපයක් තියෙනවා: resources (සම්පත්), statelessness (තත්ත්වයක් නැතිවීම), standard methods (GET, POST, PUT, DELETE), සහ client-server architecture. අපි ගොඩක් වෙලාවට දකින API වලදී, client එකට හැම request එකක්ම හදන්න අවශ්ය URL structure එක කලින්ම දැනගෙන ඉන්න ඕනේ. උදාහරණයක් විදිහට, Product එකක විස්තර ගන්න GET /products/{id}
කියලා ගහන්න ඕනේ කියලා client දන්නවා. ඒ Product එක update කරන්න PUT /products/{id}
කියලා දන්නවා. මේක හොඳයි, හැබැයි පොඩි ප්රශ්නයක් තියෙනවා.
හිතන්න, අනාගතේදී Product එකකට අලුත් operation එකක් ආවා කියලා, archive
කරන්න වගේ. එතකොට client එකට මේක කරන්න පුළුවන් URL එක මොකක්ද කියලා දන්නේ නෑ. Client එකේ code එක ආයෙත් වෙනස් කරන්න ඕනේ. ඒ වගේම, සමහර operations කරන්න පුළුවන් වෙන්නේ යම්කිසි conditions වලට අනුව විතරයි. උදාහරණයක් විදිහට, Product එකක් “active” නැත්නම් ඒක delete කරන්න බැරි වෙන්න පුළුවන්. සාමාන්ය REST API වල client එකට මේවා තේරෙන්නේ නැහැ. Client එකට මේ rules ඔක්කොම තමන්ගේ පැත්තේ (client side) maintain කරන්න වෙනවා. මේකෙන් client-server coupling එක වැඩි වෙනවා, ඒ කියන්නේ server එකේ පොඩි වෙනසක් කරද්දි වුණත් client එකත් වෙනස් කරන්න වෙනවා. අන්න ඕක තමයි අද අපි විසඳන්න හදන්නේ.
HATEOAS: Hypermedia as the Engine of Application State - අලුත් කතාව
හරි, එහෙනම් කතාවට බහිමු. HATEOAS කියන්නේ Hypermedia as the Engine of Application State. නම ඇහුවම පොඩ්ඩක් බරපතලයි වගේ පෙනුනත්, concept එක හරිම සරලයි. හිතන්නකෝ ඔයා website එකක browse කරනවා කියලා. ඔයාට Google එකට ගිහින් search කරන්න පුළුවන්, Facebook එකට ගිහින් යාළුවොත් එක්ක chat කරන්න පුළුවන්. ඔයාට මේ links කලින් ඉගෙන ගන්න ඕනෙද? නැහැනේ! වෙබ් page එකෙන්ම තමයි 'ඊළඟට ඔයාට කරන්න පුළුවන් මොනවද' කියලා links විදිහට පෙන්නන්නේ. ඔයාට කරන්න පුළුවන් දේවල් අනුව links වෙනස් වෙනවා. උදාහරණයක් විදිහට, login නැතිව ඉන්නකොට 'Login' link එක පෙන්නනවා, login වුනාම 'Logout' link එක පෙන්නනවා.
මේකේ තේරුම තමයි, client එක (අපේ web browser එක) application එකේ "state" එක වෙනස් කරන්නේ hypermedia (links) පාවිච්චි කරලා. මුලින්ම browser එකට යම්කිසි URL එකක් (e.g., www.facebook.com
) දෙනවා. ඒකෙන් එන response එකේ තියෙන links පාවිච්චි කරලා browser එක ඊළඟ action එකට යනවා. එයාට Facebook එකේ URL patterns ගැන කලින් ඉගෙන ගන්න ඕනේ නැහැ.
HATEOAS කියන්නේ මේ web browser concept එක API වලට ගේන එක. ඒ කියන්නේ, API response එකේම ඊළඟට කරන්න පුළුවන් operations වලට අදාළ links අඩංගු වෙනවා. Client එකට මේ links වලින් 'state transition' කරන්න පුළුවන්. Client එකට කලින්ම URL structures ගැන දැනගෙන ඉන්න අවශ්ය වෙන්නේ නැහැ. API එකෙන් එන response එක කියවලා, ඒ response එකේම තියෙන links පාවිච්චි කරලා ඊළඟ operation එක කරන්න පුළුවන්.
මෙතන "Engine of Application State" කියන එකෙන් කියවෙන්නේ මොකක්ද? Client එකට application එකේ වර්තමාන තත්ත්වය අනුව මොනවද කරන්න පුළුවන් කියලා server එකෙන් කියනවා. Client එකට server එකේ state එක වෙනස් කරන්න පුළුවන් වෙන්නේ server එකෙන් දීපු links හරහා විතරයි. මේක REST architectural style එකේ "Uniform Interface" constraint එකේ ඉතාම වැදගත් අංගයක්. ඒකෙන් client-server coupling එක අඩු කරනවා. ඒ කියන්නේ, server එකේ URLs වෙනස් වුණත්, resources move වුණත්, client එකට ඒ ගැන දැනගන්න අවශ්ය නැහැ. Client එක කරන්නේ response එකෙන් ආපු links අන්ධව (blindly) follow කරන එක විතරයි. මේකෙන් API එකේ maintainability, evolvability ගොඩක් වැඩි වෙනවා.
මේකේ ප්රධාන වාසි දෙකක් තියෙනවා:
- Discoverability: Client එකට 'self-discover' කරන්න පුළුවන් API එකේ තියෙන functionality.
- Evolvability: API එකේ URLs වෙනස් වුණත්, අලුත් functionality add වුණත්, client එකට ගොඩක් වෙලාවට වෙනස්කම් කරන්න ඕනේ නැහැ. මොකද client එක rely කරන්නේ hardcoded URLs වලට නෙවෙයි, response එකේ එන links වලට.
HATEOAS API එකක් හදමුද?
හරි, දැන් අපි පොඩි Product API එකක් ගැන හිතමු. සාමාන්ය Product API එකක response එකක් මෙහෙම වෙන්න පුළුවන්:
{
"id": "PROD001",
"name": "Gaming Mouse",
"price": 7500.00,
"description": "High-precision gaming mouse with RGB lighting."
}
මේකේ ගැටලුව මොකක්ද? Client එකට මේ Product එකට අදාළව මොනවා කරන්න පුළුවන්ද කියලා මේ response එකෙන් දන්නේ නෑ. එයාට කලින්ම PUT /products/{id}
, DELETE /products/{id}
වගේ URLs දැනගෙන ඉන්න ඕනේ. ඒ වගේම, Product එකක් reviews කරන්න පුළුවන් වුණත්, ඒ URL එක මොකක්ද කියලා client දන්නේ නෑ. එයාට කලින්ම documentation බලලා හරි, code එකෙන් හරි දැනගෙන ඉන්න ඕනේ.
දැන් අපි මේකට HATEOAS links එකතු කරමු. අපි HAL (Hypertext Application Language) කියන media type එක පාවිච්චි කරමු. HAL කියන්නේ HATEOAS Implement කරන්න ගොඩක් ජනප්රිය, සරල විදිහක්. HAL response එකක _links
කියන object එකක් තියෙනවා links දාන්න. ඒ වගේම _embedded
කියන object එකක් තියෙනවා nested resources embed කරන්න.
මෙන්න මේ Product එකේ HATEOAS enabled response එකක්:
{
"id": "PROD001",
"name": "Gaming Mouse",
"price": 7500.00,
"description": "High-precision gaming mouse with RGB lighting.",
"_links": {
"self": {
"href": "/products/PROD001"
},
"update": {
"href": "/products/PROD001",
"method": "PUT",
"title": "Update Product Details"
},
"delete": {
"href": "/products/PROD001",
"method": "DELETE",
"title": "Delete Product"
},
"reviews": {
"href": "/products/PROD001/reviews",
"title": "View Product Reviews",
"type": "application/hal+json"
},
"archive": {
"href": "/products/PROD001/archive",
"method": "POST",
"title": "Archive Product",
"templated": false
}
},
"_embedded": {
"category": {
"id": "CAT001",
"name": "Electronics",
"_links": {
"self": {
"href": "/categories/CAT001"
}
}
}
}
}
දැන් බලන්න, මේ response එකෙන් client එකට පැහැදිලිව පේනවා 'self' link එක (මේ resource එකට අදාළ URL එක), 'update' link එක (Product එක update කරන්න පුළුවන් URL එක PUT method එකත් එක්ක), 'delete' link එක (Delete කරන්න), 'reviews' link එක (Reviews බලන්න), අලුතින් add කරපු 'archive' link එක. Client එකට දැන් මේ links පාවිච්චි කරලා ඊළඟ operation එක කරන්න පුළුවන්. එයාට අර hardcoded URLs ගැන හිතන්න ඕනේ නැහැ.
_links
object එක ඇතුළේ තියෙන හැම key එකක්ම 'relation type' එකක් (e.g., self
, update
, delete
). මේවා semantics පෙන්නනවා. ඒකට අදාළ value එක තමයි link object එක. Link object එකේ href
(URL එක) අනිවාර්යයෙන්ම තියෙන්න ඕනේ. method
(HTTP method), title
(link එක ගැන පොඩි විස්තරයක්), templated
(URL එකේ variables තියෙනවද කියලා), type
(media type) වගේ දේවල්ත් add කරන්න පුළුවන්.
සමහර වෙලාවට, එක relation type එකකට links කීපයක් තියෙන්න පුළුවන් නම් (e.g., item
for a collection), link object එක වෙනුවට array එකක් පාවිච්චි කරන්න පුළුවන්.
GET /products
request එකකට එන response එකක් මෙන්න:
{
"_links": {
"self": {
"href": "/products?page=1"
},
"next": {
"href": "/products?page=2"
},
"create": {
"href": "/products",
"method": "POST",
"title": "Create New Product"
},
"find": {
"href": "/products{?name,category}",
"templated": true,
"title": "Find Products by Name or Category"
}
},
"_embedded": {
"products": [
{
"id": "PROD001",
"name": "Gaming Mouse",
"price": 7500.00,
"_links": {
"self": {
"href": "/products/PROD001"
},
"reviews": {
"href": "/products/PROD001/reviews"
}
}
},
{
"id": "PROD002",
"name": "Mechanical Keyboard",
"price": 12000.00,
"_links": {
"self": {
"href": "/products/PROD002"
},
"reviews": {
"href": "/products/PROD002/reviews"
}
}
}
]
},
"totalProducts": 250,
"currentPage": 1,
"itemsPerPage": 20
}
මේ response එකේ _embedded.products
යටතේ Product objects list එක තියෙනවා. ඒ වගේම _links
object එකේ self
(මේ collection එකට අදාළ link), next
(ඊළඟ page එකට යන්න පුළුවන් link), create
(අලුත් Product එකක් හදන්න පුළුවන් link) වගේ links තියෙනවා. අලුතින් find
කියන templated link එකත් add කරලා තියෙනවා, ඒකෙන් search කරන්න පුළුවන් URL pattern එකක් පෙන්නනවා.
Client එකක් HATEOAS links කොහොමද පාවිච්චි කරන්නේ?
හිතන්න, client එකක් Product එකක් update කරන්න ඕනේ කියලා. සාමාන්ය විදිහට එයාට PUT /products/{id}
කියලා URL එක හදාගන්න වෙනවා. HATEOAS එක්ක නම් client එක මුලින්ම Product resource එක GET
කරනවා. Response එක ආවම, එයා _links.update.href
කියන property එක බලනවා. ඒ URL එකට, _links.update.method
එකේ තියෙන HTTP method එක (PUT) පාවිච්චි කරලා request එක යවනවා. මේකට client side libraries (e.g., Spring HATEOAS in Java, Traverson in JavaScript, හෝ custom JSON parsers) පාවිච්චි කරන්න පුළුවන්. මේ library එක client application එකේ URL building logic replace කරනවා. Client එක දන්නේ relation (rel
) එක විතරයි (e.g., “update”, “delete”, “reviews”). මේකෙන් client එකට API එකේ URLs ගැන කිසිම දැනුමක් අවශ්ය වෙන්නේ නැහැ. Client එකට server එකේ state එක අනුව dynamic වෙන්න පුළුවන්.
HAL වගේම තවත් hypermedia formats තියෙනවා. Siren, Collection+JSON, AtomPub වගේ ඒවා ඒ අතරින් ප්රධානයි. හැබැයි concept එක හැම එකේම සමානයි – data එක්කම ඒ data වලට අදාළව කරන්න පුළුවන් actions වලට links දෙන එක.
HATEOAS වල වාසි සහ අභියෝග
හරි, දැන් අපි බලමු HATEOAS වලින් අපිට ලැබෙන වාසි සහ මේක implement කරද්දි මුහුණ දෙන්න වෙන අභියෝග මොනවද කියලා.
වාසි (Pros):
- Client Decoupling (Client ලිහිල් කිරීම): මේක තමයි ප්රධානම වාසිය. Client එක API එකේ URL structure එක ගැන "දැනුම" (knowledge) තියාගන්නේ නෑ. ඒ වෙනුවට server එකෙන් එන links පාවිච්චි කරනවා. ඒ නිසා API එකේ URLs වෙනස් වුනත් client එකට ඒ ගැන බලපෑමක් වෙන්නේ නෑ (සමහරවිට). ඒ වගේම, අලුත් operation එකක් ආවොත්, server එකේ links add කරාම client එකට ඒක අලුතෙන් "discover" කරන්න පුළුවන්.
- API Evolvability (API විකාශනය කිරීමේ හැකියාව): කාලයත් එක්ක API එක වෙනස් වෙනකොට, අලුත් features එනකොට, පරණ features අයින් වෙනකොට, links adjust කරන්න පුළුවන්. Client එකට මේ වෙනස්කම් වලට ඉක්මනින් අනුවර්තනය වෙන්න පුළුවන්. මේකෙන් 'breaking changes' එනවා අඩු වෙනවා.
- Self-documenting (ස්වයං-ලේඛනගත කිරීම): API response එකෙන් Client එකට තේරෙනවා 'මේ වෙලාවේ කරන්න පුළුවන් මොනවද' කියලා. මේක API documentation එකටත් හොඳ supplement එකක්.
- Simplified Client Logic (Client logic සරල කිරීම - සමහරවිට): Client එකට තමන්ගේ side එකේ URL building logic ගොඩක් maintain කරන්න අවශ්ය වෙන්නේ නෑ. Client එක server එකෙන් දෙන links follow කරන නිසා තමන්ගේ application state එක ගැන විතරක් හිතුවම ඇති.
- Better Client-Server Interaction: Client එකට යම් operation එකක් කරන්න පුළුවන්ද බැරිද කියලා server එකෙන්ම කියන නිසා, client එකේ unnecessary requests අඩු වෙනවා.
අභියෝග (Cons):
- Increased Response Payload Size (Response ප්රමාණය වැඩිවීම): හැම response එකකටම links add කරනකොට, response එකේ size එක වැඩි වෙන්න පුළුවන්. පොඩි response එකක් නම් ප්රශ්නයක් නෑ, හැබැයි large collections වලදී මේක impact වෙන්න පුළුවන්.
- Client Complexity in Parsing Links (Links parse කිරීමේදී Client සංකීර්ණ වීම): Client එකට දැන් hardcoded URL එකකට request එකක් යවනවට වඩා, response එක parse කරලා,
_links
object එකෙන් අදාළ link එක හොයාගෙන request එක යවන්න වෙනවා. මේකට client side libraries හෝ framework support අවශ්ය වෙන්න පුළුවන්. - Tooling Support (Tools වල සහයෝගය): සාමාන්ය REST client tools HATEOAS links වලට කෙලින්ම support කරන්නේ නැති වෙන්න පුළුවන්. Debugging සහ testing ටිකක් සංකීර්ණ වෙන්න පුළුවන්. Postman වගේ tools වලට custom scripts ලියන්න අවශ්ය වෙන්න පුළුවන්.
- Learning Curve (ඉගෙන ගැනීමේ අපහසුතාව): Team එකට HATEOAS concept එක සහ media types (HAL වගේ) ගැන ඉගෙන ගන්න වෙනවා. මුලින් ටිකක් වෙලා යන්න පුළුවන්. API Design කරද්දි ඒ links, relation types නිර්මාණය කරන එකටත් අමතර අවධානයක් දෙන්න වෙනවා.
- Over-engineering (අධික ඉංජිනේරුකරණය): හැම API එකකටම HATEOAS අවශ්ය වෙන්නේ නැහැ. හුදෙක් CRUD operations කරන සරල API එකකට HATEOAS overkill වෙන්න පුළුවන්. මේකේ වාසි උපරිමයෙන් ලැබෙන්නේ සංකීර්ණ, dynamic state changes තියෙන applications වලටයි.
මේවා ගැන හිතලා තමයි HATEOAS implement කරන්න ඕනේ. හැබැයි, longterm perspective එකකින් බැලුවොත්, විශේෂයෙන්ම complex, evolvable APIs වලට HATEOAS ගොඩක් වටිනවා.
අවසානය: අත්හදා බලන්න කාලයයි!
ඉතින් මචන්ලා, අද අපි කතා කලේ REST API Design වල තවත් වැදගත් පියවරක් ගැන – HATEOAS. මේකෙන් අපේ API එක සාමාන්ය data endpoint එකකට වඩා එහා ගිය 'application' එකක් බවට පත් වෙනවා. හරියට වෙබ් site එකක් වගේ, client එකට 'next steps' පෙන්නන්න පුළුවන්.
මුලින්ම ටිකක් අමාරුයි වගේ දැනුනත්, මේ concept එක හරිම powerful. ඔයාටත් පුළුවන් ඔයාගේ දැනට තියෙන API එකකට පොඩ්ඩක් HATEOAS links add කරලා බලන්න. HAL වගේ specification එකක් පාවිච්චි කරන එක ලේසියි. ඒ වගේම, HATEOAS clients හදන හැටි ගැන පොඩ්ඩක් explore කරන්න. Java වල Spring HATEOAS, .NET වල ASP.NET Core HATEOAS වගේ Libraries මේකට උදව් වෙනවා.
මේ ගැන ඔයාලගේ අදහස් මොනවද? ඔයාල HATEOAS පාවිච්චි කරලා තියෙනවද? තියෙනවා නම් මොන වගේ අත්දැකීම්ද ලැබුණේ? පහලින් comment එකක් දාන්න. ඔයා මේ ගැන තව ඉගෙන ගන්න කැමති නම්, නැත්නම් වෙනත් මාතෘකාවක් ගැන දැනගන්න කැමති නම් ඒකත් කියන්න. අපි ඊළඟ ලිපියෙන් හම්බවෙමු! ජයවේවා!