GraphQLRESTAPI DesignBackend EngineeringPerformance

Day 04 — GraphQL vs REST — কোনটা কখন? সত্যিকারের trade-off 🤔

June 10, 20267 min read30 Days Of Backend Engineering

মনে করুন, আপনি একটা ট্যুর দিতে যাবেন। এই জন্য একটু রিসার্চ করলেন। কোথায় যাবেন তার জন্য অনেকগুলো প্লেস শর্টলিস্ট করলেন। এখন এখান থেকে আপনার জন্য একটা প্লেস চুজ করা একটু কষ্ট হয়ে যাচ্ছে কারণ সবগুলো প্লেসই আকর্ষণীয়। অনেক রিসার্চ করার পরে ২টা প্লেসে আপনি আটকে গেছেন। আপনি কোনোভাবেই একটাতে শিফ্ট করতে পারছেন না। কোইনসিডেন্টলি ২টা প্লেসের খরচই প্রায় একই। এখান থেকে আপনি একটা জিনিস বুঝতে পারলেন যে—ফ্লেক্সিবিলিটি বেশি থাকলে চুজ করা একটু কষ্ট হয়ে যায়।

এই প্রবলেমের সলিউশন থেকে বের হওয়ার জন্য আপনি আপনার এক বন্ধুর পরামর্শ নিতে গেলেন। আপনার কথা সে শুনলো। এর পর কিছুক্ষণ ভাবলো। প্রশ্ন করলো, "তুই আসলে কী জন্য ট্যুরটা দিতে চাচ্ছিস? তোর উদ্দেশ্যটা কী?"। এই কথা শোনার পর আপনি তাকে সব কিছুই বললেন। তারপর সে আপনাকে কিছু কথা বললো, "এখানে প্লেসটা কিন্তু মুখ্য না, মুখ্য হচ্ছে তোর প্রয়োজনীয়তা। এখানে এখানে গেলে তোর সব উদ্দেশ্য সাকসেসফুলি পূরণ হবে। এখন তুই যে সিদ্ধান্তটা নিতে পারছিস না, তার মেইন রিজন হচ্ছে—তোর প্রয়োজনীয়তার ওপরে ফোকাস করিসনি। আরও একটা বিষয় হচ্ছে ওই প্লেস সম্পর্কে তোর খুব বেশি আইডিয়া নেই।"

এর পরে আপনি আরামে ট্যুরে চলে গেলেন। আপনার উদ্দেশ্য হাসিল হলো। সব কিছু একদম পারফেক্ট।

এখন আপনি বলবেন এখানে ট্যুরের সাথে টেকনোলজির সম্পর্ক কী? সম্পর্ক আছে। এখানে আপনি একটা জিনিস রিয়ালাইজ করতে পারবেন যে, ফ্লেক্সিবিলিটি যত বেশি, সেখানে প্রবলেম তত বেশি। এই প্রবলেম সলভ করতে হলে নলেজ এবং ফোকাস দুইটা জিনিসই দরকার।

REST এবং GraphQL, দুইটার ফান্ডামেন্টাল কাজ হচ্ছে সার্ভারের সাথে কমিউনিকেট করে ডাটা ট্রান্সফার করা। এখন আপনার সিদ্ধান্ত নেওয়া কিন্তু একটু প্রবলেমেটিক, কারণ দুইটাই প্রায় একই কাজের জন্য।

এই প্রবলেমকে সলভ করতে হলে আগে যে স্ট্র্যাটেজি ফলো করে একটা প্লেস সিলেক্ট হয়েছে, একই উপায়ে সলভ করতে হবে। আগে আপনার রিকোয়ারমেন্ট সম্পর্কে স্পষ্ট ধারণা নিতে হবে—আপনার অ্যাপ্লিকেশনের আসলে কী দরকার। এর পরে REST ও GraphQL সম্পর্কে নলেজ গ্যাদার করতে হবে, তাদের ইউজার কেস সম্পর্কে জানতে হবে। সবশেষে আপনার অ্যাপ্লিকেশন রিকোয়ারমেন্টের সাথে যার ইউজার কেস মিলে যাবে, সেটাই বেছে নিতে হবে।


REST-এর সমস্যাটা কোথায়?

একটা জিনিস চিন্তা করুন—একটা প্রবলেম সলভ করার জন্য যখন একাধিক অপশন থাকে, তখন স্বাভাবিকভাবেই বোঝা যায় যে অপশনগুলোর কিছু না কিছু ডিসঅ্যাডভান্টেজ থাকেই। এগুলো জানলেই আপনার অপশন চুজ করতে সুবিধা হবে।

REST সম্পর্কে তো আগেই জেনে গেছেন (আগের আর্টিকেল থেকে)। ওখানে REST-এর ডিসঅ্যাডভান্টেজ নিয়ে তেমন কোনো আলোচনা হয়নি।

মনে করুন আপনার একটা মোবাইল অ্যাপ আছে, ওই অ্যাপের প্রোফাইল পেজে নিচের এই জিনিসগুলো দেখাতে হবে:

  • ইউজারের নাম, ফটো
  • লেটেস্ট ৩টা পোস্ট
  • ফলোয়ার কাউন্ট
এখন এটা সলভ করতে হলে কতগুলো API Call লাগবে?
GET /users/5           → ইউজারের নাম, ফটো
GET /users/5/posts     → সব পোস্ট ফেচ করে, লজিক দিয়ে ফিল্টার করতে হবে।
GET /users/5/followers → ফলোয়ার কাউন্ট

৩টা API Call করা লাগবে। এখানে API Call সংখ্যার চেয়ে আরও একটা জিনিস ম্যাটার করছে।

এখানে কয়েকটি জিনিস খেয়াল করুন:

  • ইউজারের জন্য লাগবে শুধু নাম আর ইমেজ, কিন্তু ফেচ করতে হচ্ছে ইউজারের সব কিছু।
  • পোস্ট লাগবে মাত্র ৩টি, কিন্তু ফেচ হচ্ছে অনেকগুলো পোস্টের ডাটা।
  • ফলোয়ার কাউন্টের জন্য লাগবে জাস্ট একটা নম্বর, কিন্তু পুরো লিস্ট ফেচ করতে হচ্ছে।
এখানে অনেক অপ্রয়োজনীয় ডাটা ফেচ হচ্ছে, এই ডাটাগুলোর জন্য ব্যান্ডউইথ নষ্ট হচ্ছে, সাথে রেসপন্স সাইজ বড় হওয়ার জন্য API Call Latency-ও বেড়ে যাচ্ছে।

এই সমস্যার নামই হলো Over-fetching আর Under-fetching


GraphQL কীভাবে এটা সলভ করে?

এই প্রবলেমটা সলভ করে GraphQL। কীভাবে সলভ করে? তাহলে GraphQL কীভাবে কাজ করে সেটা জানতে হবে।

মূলত GraphQL-এ একটি মাত্র Endpoint থাকে (/graphql), এই এন্ডপয়েন্টের মধ্যে Query পাস করতে হয়। GraphQL হলো একটি Schema-driven প্রযুক্তি। সার্ভার আগে একটি স্ট্রং টাইপ কন্ট্রাক্ট ডিফাইন করে যাকে Schema (SDL - Schema Definition Language) বলা হয়:

# ১. Server Schema (কন্ট্রাক্ট)
type Post {
  id: ID!
  title: String!
  date: String!
}

type User {
  id: ID!
  name: String!
  avatar: String!
  posts(limit: Int = 5): [Post!]!
  followerCount: Int!
}

type Query {
  user(id: ID!): User
}

আর ক্লায়েন্ট তার দরকার অনুযায়ী ঠিক যতটুকু ডেটা প্রয়োজন ততটুকুই ব্যাকএন্ড থেকে চেয়ে নেয়:

# ২. Client Query (অনুরোধ)
query {
  user(id: 5) {
    name
    avatar
    posts(limit: 3) {
      title
      date
    }
    followerCount
  }
}

এই একটা কলেই পুরো প্রয়োজনীয় ডাটা সার্ভার থেকে নির্দিষ্ট ফরম্যাটে চলে আসবে। কোনো ওভার-ফেচিং বা আন্ডার-ফেচিং থাকবে না।


GraphQL-এর মূল তিনটি অপারেশন ⚙️

REST-এ যেমন GET, POST, PUT, DELETE-এর মতো HTTP মেথড থাকে, GraphQL-এ তিনটি প্রধান রুট অপারেশন রয়েছে:

  1. Query (Read): ডেটা রিড করার জন্য (REST-এর GET-এর অনুরূপ)।
  2. Mutation (Write): ডেটা তৈরি, আপডেট বা ডিলিট করার জন্য (REST-এর POST, PUT, DELETE-এর মতো)।
mutation CreateNewPost {
  createPost(title: "GraphQL Mastery", content: "Backend deep dive") {
    id
    title
    createdAt
  }
}
  1. Subscription (Real-time): সার্ভারে কোনো ইভেন্ট ঘটলে ক্লায়েন্টের কাছে লাইভ ডেটা পুশ করার জন্য। এটি ব্যাকগ্রাউন্ডে WebSockets ব্যবহার করে পারসিস্টেন্ট কানেকশন বজায় রাখে।
subscription OnNewComment($postId: ID!) {
  commentAdded(postId: $postId) {
    id
    author
    content
  }
}

Schema Introspection ও অটোমেটিক ডকুমেন্টেশন 🔍

GraphQL-এর দারুণ একটি সুবিধা হলো Introspection (__schema)। ক্লায়েন্ট বা ডেভেলপার সরাসরি সার্ভারকে কোড দিয়ে জিজ্ঞাসা করতে পারে: "তোমার কাছে কী কী টাইপ, কী কী কুয়েরি আর আর্গুমেন্ট আছে?"

এর ফলে:

  • GraphiQL / Apollo Studio: সার্ভার রান করলেই অটোমেটিক ইন্টারঅ্যাকটিভ API ডকুমেন্টেশন আর টেস্ট কনসোল তৈরি হয়ে যায়। কোনো ম্যানুয়াল Swagger ফাইল লিখতে হয় না।
  • Code Generation: ফ্রন্টএন্ডে GraphQL Code Generator ব্যবহার করে ব্যাকএন্ড স্কিমা থেকে হুবহু TypeScript Types এবং SDK স্বয়ংক্রিয়ভাবে তৈরি করে নেওয়া যায়।

তাহলে সব জায়গায় GraphQL ব্যবহার করব না কেন?

সবকিছুরই ট্রেড-অফ থাকে। GraphQL বেশ কিছু জায়গায় অতিরিক্ত জটিলতা তৈরি করে:

১. N+1 Problem

GraphQL-এ প্রতিটি ফিল্ডের জন্য আলাদা `Resolver` ফাংশন এক্সিকিউট হয়। যদি আপনি ৫০ জন ইউজারের লিস্ট চান এবং প্রতি ইউজারের সাথে তাদের `posts` চান, তাহলে ১টি কুয়েরি দিয়ে ইউজার ফেচ হবে এবং প্রতি ইউজারের পোস্টের জন্য ডাটাবেসে আলাদা আলাদা আরও ৫০টি কুয়েরি এক্সিকিউট হবে (১ + ৫০ = ৫১টি কুয়েরি!)।

এখানে উদ্ধারকর্তা হিসেবে কাজ করে Facebook-এর তৈরি DataLoader:

  • Batching: ইভেন্ট লুপের এক ফ্রেমে আসা সব রিকোয়েস্টকে জমা করে একটিমাত্র SELECT FROM posts WHERE userId IN (...) কুয়েরি চালায়।
  • Caching: একই রিকোয়েস্টে একই ইউজারের ডেটা একাধিকবার প্রয়োজন হলে তা ডাটাবেস থেকে না টেনে মেমোরি থেকে দিয়ে দেয়।
(Day 11-এ আমরা কোডসহ N+1 ও DataLoader-এর গভীর ব্যবচ্ছেদ দেখব)*।

২. Caching জটিলতা

REST-এ URL ধরে ব্রাউজার ও CDN লেভেলে খুব সহজে HTTP ক্যাশিং করা যায়। কিন্তু GraphQL-এ সব রিকোয়েস্ট একই `/graphql` এন্ডপয়েন্টে `POST` মেথডে পাঠানো হয়। ফলে প্রথাগত HTTP ক্যাশিং সরাসরি কাজ করে না। এখানে জটিল Normalized Client Cache (যেমন Apollo Client InMemoryCache) বা Persisted Queries ব্যবহার করতে হয়।

৩. Query Complexity ও Security রিস্ক

GraphQL ক্লায়েন্টকে নিজের মতো করে কুয়েরি সাজানোর পূর্ণ স্বাধীনতা দেয়। কোনো ক্ষতিকর ইউজার বা হ্যাকার চাইলে এমন একটি রিকার্সিভ নেস্টেড কুয়েরি পাঠাতে পারে:
query MaliciousQuery {
  user {
    friends {
      friends {
        friends {
          friends { name }
        }
      }
    }
  }
}

এই কুয়েরি সার্ভার এবং ডাটাবেসের সিপিইউ ১০০% দখল করে পুরো অ্যাপ্লিকেশন ক্র্যাশ করিয়ে দিতে পারে (DDoS Attack)। তাই প্রোডাকশন GraphQL সার্ভারে সবসময় Query Depth Limiting এবং Query Cost Analysis কনফিগার করে রাখতে হয়, যাতে নির্দিষ্ট লিমিটের বেশি জটিল কুয়েরি সার্ভার সরাসরি রিজেক্ট করে দেয়।


কখন কোনটা?

REST ব্যবহার করুন যখন —

  • Simple CRUD operations
  • Public API যেটা বাইরে বিভিন্ন থার্ড-পার্টি ডেভেলপাররা consume করবে
  • Caching critical (CDN লেভেলে স্ট্যাটিক ক্যাশিং দরকার)
  • টিম GraphQL-এর জটিলতায় ঢুকতে চায় না
**GraphQL ব্যবহার করুন যখন —**
  • Complex, deeply nested ডাটা রিলেশনশিপ দরকার
  • Multiple clients (web, mobile, smart watch) আলাদা আলাদা ডাটা সাইজ ও ফরম্যাট চায়
  • Rapid UI iteration দরকার—ফ্রন্টএন্ডের সামান্য পরিবর্তনের জন্য ব্যাকএন্ডে বারবার নতুন এন্ডপয়েন্ট বানানোর ঝামেলা এড়াতে চান

বটম লাইন

ভবিষ্যতে একটা প্রবলেম রিসলভ করার জন্য অনেক অপশন আসতে পারে। কিন্তু সিলভার বুলেট বলে কিছু নেই। রিকোয়ারমেন্ট অনুযায়ী বেস্ট অপশনটাই বেছে নিতে হবে।

আপনার Current Project-এ কোনটা ব্যবহার করছেন? কমেন্টে জানান! 👇