API Style: REST, GraphQL, gRPC, WebSocket
কোন কাজে কোন ধরনের API
API হলো দুইটা program এর মধ্যে কথা বলার চুক্তি। তোমার Flutter app আর backend এর মধ্যে, বা তোমার backend আর bKash এর payment gateway এর মধ্যে। চুক্তিটা কেমন হবে তার কয়েকটা জনপ্রিয় ধরন আছে: REST, GraphQL, gRPC, WebSocket, SSE, Webhook। প্রতিটা আলাদা সমস্যার জন্য ভালো। এই অধ্যায়ে দেখবো কোনটা কখন, আর ভালো API কীভাবে design করতে হয়।
#এক নজরে
| ধরন | কীভাবে | কখন ভালো | উদাহরণ |
|---|---|---|---|
| REST | URL = জিনিস, HTTP method = কাজ | সাধারণ CRUD, public API, mobile app | GET /orders/42 |
| GraphQL | একটা endpoint, client বলে কোন field চাই | অনেক রকম screen, অনেক রকম data | Facebook, GitHub API |
| gRPC | Binary, কঠোর schema (protobuf), HTTP/2 | Server থেকে server, দ্রুত আর ছোট | Microservice এর ভেতরের কথা |
| WebSocket | স্থায়ী দুই দিকের connection | Chat, live game, collaborative editing | WhatsApp web |
| SSE | Server থেকে client এ একমুখী stream | Live score, notification, AI উত্তর stream | ChatGPT ধরনের typing |
| Webhook | ঘটনা ঘটলে server তোমার URL এ call করে | অন্য system থেকে খবর পাওয়া | Payment সফল হলে gateway জানায় |
#REST: সবচেয়ে প্রচলিত
REST এর মূল ধারণা: সব কিছু একটা resource (জিনিস), প্রতিটা resource এর একটা URL, আর HTTP method দিয়ে বলো কী করতে চাও।
| কাজ | Method | URL | সফল হলে |
|---|---|---|---|
| সব order এর list | GET |
/orders |
200 |
| একটা order দেখা | GET |
/orders/42 |
200 |
| নতুন order | POST |
/orders |
201 |
| পুরো order বদলানো | PUT |
/orders/42 |
200 |
| কিছু field বদলানো | PATCH |
/orders/42 |
200 |
| মুছে ফেলা | DELETE |
/orders/42 |
204 |
| একটা order এর item গুলো | GET |
/orders/42/items |
200 |
#ভালো REST URL এর নিয়ম
✓ GET /restaurants/15/menu-items
✓ POST /orders
✓ GET /orders?status=pending&page=2&limit=20
✗ GET /getAllOrders ← URL এ ক্রিয়াপদ না, method ই ক্রিয়া
✗ POST /order/create ← একবচন আর ক্রিয়াপদ
✗ GET /orders/delete/42 ← GET দিয়ে কখনো কিছু বদলাবে না- বহুবচন noun:
/orders,/users - ক্রিয়া নয়, method: কাজটা method দিয়ে বোঝাও
- Filter, sort, page query string এ:
?status=pending&sort=-created_at - কিছু কাজ CRUD এ মেলে না: যেমন order বাতিল করা। তখন sub-resource বা action:
POST /orders/42/cancel। এটা গ্রহণযোগ্য।
#Safe আর idempotent
| Method | Safe (কিছু বদলায় না) | Idempotent (একবার আর দশবার একই ফল) |
|---|---|---|
| GET | হ্যাঁ | হ্যাঁ |
| PUT | না | হ্যাঁ |
| DELETE | না | হ্যাঁ |
| PATCH | না | সাধারণত না |
| POST | না | না |
এটা কেন জরুরি: mobile network দুর্বল হলে Flutter app request পাঠালো, server কাজ করলো, কিন্তু উত্তর ফেরার পথে network কেটে গেল। App জানে না কাজ হয়েছে কিনা, তাই আবার পাঠায়। PUT বা DELETE হলে সমস্যা নেই। কিন্তু POST /payments আবার গেলে দুইবার টাকা কাটবে। সমাধান হলো idempotency key: client প্রতিটা নতুন কাজের জন্য একটা unique id পাঠায়, server সেই id আগে দেখে থাকলে আবার কাজ না করে আগের ফলাফল ফেরত দেয়। বিস্তারিত Resilience আর Payment case study তে।
#Pagination
১০ লাখ order একসাথে পাঠানো যায় না। দুই রকম pagination:
Offset pagination: GET /orders?page=3&limit=20 → database এ LIMIT 20 OFFSET 40
- সহজ, "পেজ ৩" এ সরাসরি যাওয়া যায়
- সমস্যা: পেছনের পেজে গেলে ধীর (database কে আগের সব row গুনে বাদ দিতে হয়), আর মাঝে নতুন data এলে কিছু item দুইবার আসে বা বাদ পড়ে
Cursor pagination: GET /orders?limit=20&after=eyJpZCI6MTAyMH0
- Server শেষ item এর পরিচয় (cursor) দেয়, পরের request এ সেটা পাঠাও → database এ
WHERE id < 1020 ORDER BY id DESC LIMIT 20 - দ্রুত, যত গভীরেই যাও, আর নতুন data এলেও ঠিক থাকে
- "পেজ ৩" এ সরাসরি যাওয়া যায় না
Flutter এর infinite scroll list (feed, order history) এর জন্য cursor pagination সবচেয়ে উপযুক্ত।
#Versioning
API একবার mobile app এ চলে গেলে বদলানো কঠিন, কারণ পুরনো version এর app অনেকের phone এ থেকে যায় (অনেকে মাসের পর মাস update করে না)। তাই:
- URL এ version:
/v1/orders,/v2/orders(সবচেয়ে সহজ আর প্রচলিত) - নতুন field যোগ করা নিরাপদ, পুরনো app সেটা উপেক্ষা করবে
- Field মুছে ফেলা বা নাম/type বদলানো মানে breaking change → নতুন version
#Error এর ধরন একরকম রাখো
{
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "আপনার account এ যথেষ্ট টাকা নেই",
"details": {"needed": 55000, "available": 20000}
}
}Mobile app code দেখে সিদ্ধান্ত নেবে (কোন screen দেখাবে), আর message user কে দেখাবে। সব endpoint এ একই গঠন রাখলে app এর error handling এক জায়গায় লেখা যায়।
#GraphQL: client নিজে বলে কী চাই
REST এ order details screen এর জন্য হয়তো তিনটা call লাগে: /orders/42, /restaurants/15, /riders/7। আবার /orders/42 এমন ৩০টা field দেয় যার ৫টা লাগে। প্রথমটাকে বলে under-fetching, দ্বিতীয়টা over-fetching।
GraphQL এ একটা request এ ঠিক যা দরকার:
query {
order(id: 42) {
status
total
restaurant { name phone }
rider { name location { lat lng } }
}
}সুবিধা: কম request, কম data (mobile data খরচ কমে), frontend team backend এর জন্য অপেক্ষা না করে নতুন screen বানাতে পারে।
অসুবিধা:
- HTTP caching কঠিন (সব request একটা endpoint এ
POST) - খারাপ query (অনেক গভীর nested) server কে ধসিয়ে দিতে পারে, তাই query এর জটিলতার সীমা লাগে
- N+1 সমস্যা: ২০টা order এর প্রতিটার restaurant আলাদা query তে আনলে ২১টা database query। DataLoader এর মতো কৌশল লাগে।
- শেখা আর চালানো REST এর চেয়ে জটিল
কখন: অনেক রকম client (web, mobile, admin) আর অনেক রকম screen, যাদের data এর চাহিদা আলাদা। ছোট team আর সাধারণ app এ REST ই যথেষ্ট।
#gRPC: server থেকে server
gRPC তে আগে একটা .proto file এ চুক্তি লেখা হয়:
service PaymentService {
rpc Charge(ChargeRequest) returns (ChargeResponse);
}
message ChargeRequest {
string order_id = 1;
int64 amount_paisa = 2;
}এখান থেকে Python, Go, Java সব ভাষার জন্য code তৈরি হয়। Data যায় binary তে (JSON এর চেয়ে ছোট আর দ্রুত), HTTP/2 এর উপর।
কখন: Microservice গুলোর নিজেদের মধ্যে কথা, যেখানে গতি জরুরি আর দুই পক্ষই তোমার নিয়ন্ত্রণে। Browser সরাসরি gRPC বলতে পারে না, তাই public API বা web app এর জন্য সাধারণত REST।
#Real-time: WebSocket, SSE, polling
Order এর status বদলালে user কে কীভাবে জানাবে? চারটা উপায়:
| Short polling | Long polling | SSE | WebSocket | |
|---|---|---|---|---|
| দিক | client → server | client → server | server → client | দুই দিকেই |
| বিলম্ব | polling এর ব্যবধান পর্যন্ত | প্রায় সাথে সাথে | সাথে সাথে | সাথে সাথে |
| Server এর চাপ | অনেক অদরকারি request | মাঝারি | কম | কম, কিন্তু অনেক খোলা connection |
| জটিলতা | সবচেয়ে সহজ | সহজ | সহজ (সাধারণ HTTP) | বেশি (connection রাখা, reconnect, scale) |
| উদাহরণ | ছোট admin dashboard | পুরনো chat | Live score, AI উত্তর stream, notification | Chat, multiplayer game, live location দুই দিকে |
কোনটা বেছে নেবে:
- Server শুধু খবর পাঠাবে (order status, live score, AI এর লেখা) → SSE
- দুই দিকেই ঘন ঘন কথা (chat, game, rider এর location পাঠানো আর পাওয়া) → WebSocket
- কয়েক মিনিটে একবার update হলেই চলে → polling যথেষ্ট, জটিলতা নিও না
- App বন্ধ থাকলেও খবর দিতে হবে → এগুলোর কোনোটাই না, push notification (FCM/APNs)। Mobile OS background এ app এর connection বন্ধ করে দেয়।
#Webhook: "আমি তোমাকে জানাবো"
Payment gateway তে user টাকা দিলো। তোমার server কীভাবে জানবে? বারবার জিজ্ঞেস করার (polling) বদলে gateway নিজে তোমার একটা URL এ POST করে:
POST https://api.yourshop.com/webhooks/payment
X-Signature: sha256=8f3a...
{"event": "payment.succeeded", "order_id": "ORD-1042", "amount": 55000, "txn_id": "TX9F2K"}Webhook নেওয়ার নিয়ম:
- Signature যাচাই করো। যে কেউ তোমার URL এ নকল "payment সফল" পাঠাতে পারে। Gateway একটা গোপন key দিয়ে sign করে, তুমি মিলিয়ে দেখো।
- একই webhook দুইবার আসতে পারে। Gateway নিশ্চিত না হলে আবার পাঠায়।
txn_idদেখে আগে সামলানো হয়ে থাকলে আবার কিছু করো না (idempotent)। - দ্রুত
200দাও, ভারী কাজ পরে। Webhook পেয়ে queue তে রেখে সাথে সাথে উত্তর দাও। না হলে gateway timeout ভেবে আবার পাঠাবে। - শুধু webhook এর উপর ভরসা না। মাঝে মাঝে webhook হারায়। একটা background job দিয়ে মাঝে মাঝে gateway কে জিজ্ঞেস করে মিলিয়ে নাও (reconciliation)।
#অনুশীলন
#নিজেকে যাচাই করো
`POST /payments` দুইবার গেলে কী সমস্যা, আর সমাধান কী?
POST idempotent না, তাই দুইবার টাকা কাটতে পারে। সমাধান: client প্রতিটা নতুন payment এর জন্য একটা unique idempotency key পাঠায়। Server আগে সেই key দেখে থাকলে আবার কাজ না করে আগের ফলাফল ফেরত দেয়।
Infinite scroll feed এর জন্য offset নাকি cursor pagination?
Cursor pagination। এটা গভীর পেজেও দ্রুত (database index ব্যবহার করে সরাসরি পরের অংশে যায়), আর মাঝে নতুন item এলেও কোনো item দুইবার আসে না বা বাদ পড়ে না।
GraphQL এর দুইটা সুবিধা আর দুইটা অসুবিধা বলো।
সুবিধা: client ঠিক যা দরকার সেই field পায় (over/under-fetching নেই), এক request এ অনেক resource। অসুবিধা: HTTP caching কঠিন, খারাপ বা গভীর query server কে ধসাতে পারে, N+1 query সমস্যা।
AI এর উত্তর অক্ষরে অক্ষরে দেখাতে SSE নাকি WebSocket?
SSE। এখানে data শুধু server থেকে client এ যায়, আর SSE সাধারণ HTTP এর উপর চলে, তাই সহজ। দুই দিকে ঘন ঘন কথা লাগলে তবেই WebSocket।
Webhook নেওয়ার সময় signature কেন যাচাই করতে হয়?
Webhook URL টা public। যাচাই না করলে যে কেউ নকল "payment সফল" পাঠিয়ে বিনা টাকায় order confirm করিয়ে নিতে পারে। Signature প্রমাণ করে message টা আসল gateway থেকেই এসেছে আর পথে বদলায়নি।
চিহ্ন দিলে sidebar আর রোডম্যাপে এই অধ্যায়ের station ভরে যাবে।