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 এর নিয়ম

Text
✓ 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 এর ধরন একরকম রাখো

JSON
{
  "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 এ ঠিক যা দরকার:

graphql
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 এ চুক্তি লেখা হয়:

protobuf
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 প্রতি ৫ সেকেন্ডে "কিছু হলো?" অনেক অদরকারি request Long polling "কিছু হলে জানাও", server ধরে রাখে ঘটনা ঘটলে উত্তর, তারপর আবার নতুন request SSE একবার connect, server বারবার পাঠায় (একমুখী) update, update... WebSocket স্থায়ী connection, দুই দিকেই যেকোনো সময়
উপর থেকে নিচে: সবচেয়ে সহজ থেকে সবচেয়ে শক্তিশালী। বেশিরভাগ কাজে SSE বা WebSocket।
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 করে:

HTTP
POST https://api.yourshop.com/webhooks/payment
X-Signature: sha256=8f3a...

{"event": "payment.succeeded", "order_id": "ORD-1042", "amount": 55000, "txn_id": "TX9F2K"}

Webhook নেওয়ার নিয়ম:

  1. Signature যাচাই করো। যে কেউ তোমার URL এ নকল "payment সফল" পাঠাতে পারে। Gateway একটা গোপন key দিয়ে sign করে, তুমি মিলিয়ে দেখো।
  2. একই webhook দুইবার আসতে পারে। Gateway নিশ্চিত না হলে আবার পাঠায়। txn_id দেখে আগে সামলানো হয়ে থাকলে আবার কিছু করো না (idempotent)।
  3. দ্রুত 200 দাও, ভারী কাজ পরে। Webhook পেয়ে queue তে রেখে সাথে সাথে উত্তর দাও। না হলে gateway timeout ভেবে আবার পাঠাবে।
  4. শুধু 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 ভরে যাবে।