পাঠ ৪৪ · ৫৮-এর মধ্যে · মডিউল ১০
Home / Courses / Full-Stack Web Frameworks / GraphQL বনাম REST

GraphQL বনাম REST — কখন কোনটি ব্যবহার করবেন

GraphQL vs REST — When to Use Which
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • Over-fetching ও under-fetching — REST-এর ফিক্সড-শেপ রেসপন্সের দুটি বাস্তব সমস্যা
  • একই ডেটার উপর REST-স্টাইল endpoint ও L43-এর GraphQL execute() ইঞ্জিন চালিয়ে প্রকৃত ফিল্ড-সংখ্যার পার্থক্য গণনা করা
  • কখন REST-এর সরলতা ও cacheability GraphQL-এর নমনীয়তার চেয়ে বেশি মূল্যবান
  • দুটোর মধ্যে সিদ্ধান্ত নেওয়ার বাস্তব মানদণ্ড

১ · Over-fetching সমস্যা — REST-এর ফিক্সড শেপ

একটি সাধারণ REST endpoint, যেমন GET /users/1, সবসময় একই শেপের রেসপন্স রিটার্ন করে — সার্ভার-সাইড কোডে যতগুলো ফিল্ড ফেরত দিতে বলা হয়েছে, ঠিক ততগুলোই আসে, ক্লায়েন্ট আসলে কী চেয়েছিল তা বিবেচনা না করেই। একটি মোবাইল অ্যাপ যদি শুধু ইউজারের নাম দেখাতে চায়, তবুও সে পুরো ইউজার অবজেক্ট (ঠিকানা, জন্মতারিখ, বায়ো — সব) পাবে — একে over-fetching বলে।

২ · একই ডেটা, দুটি পদ্ধতি — প্রকৃত ফিল্ড-সংখ্যা গণনা

নিচের কোড সেলে একই ইউজার রেকর্ডের (৮টি ফিল্ডসহ) উপর দুটো পদ্ধতি চালানো হয়েছে — প্রথমে একটি REST-স্টাইল rest_get_user() ফাংশন, যা সবসময় সবগুলো ফিল্ড ফেরত দেয়; তারপর L43-এর SCHEMA/ execute() প্যাটার্নের একটি ছোট সংস্করণ, যেখানে ক্লায়েন্টের কোয়েরি মাত্র ২টি ফিল্ড (name, email) চায়। দুটো রেজাল্টের প্রকৃত len() গুনে পার্থক্যটা সংখ্যায় দেখানো হয়েছে।

Python
# ---------- আসল ডেটা -- একজন ইউজারের ৮টি ফিল্ড ----------
users_db = {
    1: {
        "id": 1,
        "name": "Farhana Akter",
        "email": "farhana@example.com",
        "phone": "+880-1XXXXXXXXX",
        "address": "ঢাকা, বাংলাদেশ",
        "birth_date": "1998-04-12",
        "avatar_url": "https://example.com/avatars/1.png",
        "bio": "ব্যাক-এন্ড ডেভেলপার, GraphQL নিয়ে কাজ করছি।",
    }
}


# ---------- পদ্ধতি ১ -- REST-স্টাইল ফিক্সড-শেপ endpoint ----------
def rest_get_user(user_id):
    u = users_db[user_id]
    return {
        "id": u["id"],
        "name": u["name"],
        "email": u["email"],
        "phone": u["phone"],
        "address": u["address"],
        "birth_date": u["birth_date"],
        "avatar_url": u["avatar_url"],
        "bio": u["bio"],
    }  # সবসময় সবগুলো ফিল্ড -- ক্লায়েন্ট যা-ই চাক না কেন


# ---------- পদ্ধতি ২ -- L43-এর প্যাটার্নের ছোট সংস্করণ ----------
SCHEMA = {
    "Query": {"user": {"resolve": lambda root, args: users_db.get(args.get("id")), "type": "User"}},
    "User": {f: {"resolve": (lambda field: lambda obj, args: obj[field])(f), "type": "scalar"}
             for f in ["id", "name", "email", "phone", "address", "birth_date", "avatar_url", "bio"]},
}


def execute(query_shape, root, type_name):
    result = {}
    for field_name, sub in query_shape.items():
        field_def = SCHEMA[type_name][field_name]
        args = sub.get("_args", {})
        value = field_def["resolve"](root, args)
        if field_def["type"] == "scalar":
            result[field_name] = value
        else:
            select = sub.get("_select", [])
            child_type = field_def["type"]
            result[field_name] = {f: SCHEMA[child_type][f]["resolve"](value, {}) for f in select}
    return result


graphql_query = {"user": {"_args": {"id": 1}, "_select": ["name", "email"]}}  # ক্লায়েন্ট মাত্র ২টি ফিল্ড চায়

rest_response = rest_get_user(1)
graphql_response = execute(graphql_query, root=None, type_name="Query")["user"]

print("REST রেসপন্সের ফিল্ডগুলো:", sorted(rest_response.keys()))
print("GraphQL রেসপন্সের ফিল্ডগুলো:", sorted(graphql_response.keys()))

rest_count = len(rest_response)
graphql_count = len(graphql_response)
print(f"\nREST ফিল্ড-সংখ্যা: {rest_count}")
print(f"GraphQL ফিল্ড-সংখ্যা: {graphql_count}")
print(f"অতিরিক্ত/অপ্রয়োজনীয় ফিল্ড (over-fetching): {rest_count - graphql_count}")

    
rest_count সবসময় 8 হবে (dict-এ ৮টি key হার্ডকোড করা আছে) — ক্লায়েন্ট আসলে যা-ই চাক না কেন। graphql_count হবে 2, কারণ graphql_query-এর _select লিস্টে মাত্র দুটো নাম আছে, আর execute()-এর dict comprehension শুধু সেই দুটোর জন্যই resolver কল করে। তাই rest_count - graphql_count = 8 - 2 = 6 — এই ৬টি ফিল্ড REST অপ্রয়োজনে পাঠিয়েছে।

৩ · Under-fetching ও REST যখন এখনও জেতে

Over-fetching-এর বিপরীত সমস্যাও আছে — under-fetching। ধরা যাক ক্লায়েন্টের ইউজারের নাম ও তার পোস্টগুলোর টাইটেল দুটোই দরকার। REST-এ এর জন্য সাধারণত দুটি আলাদা রিকোয়েস্ট লাগে — GET /users/1 তারপর GET /users/1/posts — যেখানে L43-এর GraphQL কোয়েরি user { name posts { title } }-এর মতো একটি মাত্র রিকোয়েস্টেই দুটো পায় (L45-L46-এ আমরা এই ধরনের "কতগুলো নেটওয়ার্ক কল লাগলো" গণনার প্যাটার্নটি আরও গভীরভাবে ব্যবহার করব)।

তবু এর মানে এই নয় যে GraphQL সবসময় ভালো পছন্দ। REST-এর কিছু বাস্তব সুবিধা আছে যা GraphQL সহজে দিতে পারে না — একটি GET রিকোয়েস্টের URL নিজেই একটি ক্যাশ-কী, তাই ব্রাউজার, CDN, বা প্রক্সি সরাসরি সেটা ক্যাশ করতে পারে; GraphQL-এর কোয়েরি সাধারণত POST বডিতে থাকে বলে এই স্ট্যান্ডার্ড HTTP ক্যাশিং সরাসরি কাজ করে না। ছোট, সরল API-তে যেখানে প্রায় সব ক্লায়েন্টই একই ডেটা চায়, REST-এর সরলতা ও পরিণত টুলিং (ডকুমেন্টেশন, রেট-লিমিটিং, মনিটরিং) প্রায়ই GraphQL-এর নমনীয়তার প্রয়োজনকেই ছাড়িয়ে যায়।

মূল কথা · Key takeaway

REST-এর ফিক্সড-শেপ রেসপন্স over-fetching (অপ্রয়োজনীয় ফিল্ড) ও under-fetching (একাধিক রিকোয়েস্ট) — দুটো সমস্যাই তৈরি করতে পারে, যা GraphQL-এর client-specified query সমাধান করে। কিন্তু REST-এর সরলতা ও স্ট্যান্ডার্ড HTTP ক্যাশিং-এর সুবিধা সব প্রজেক্টে GraphQL দিয়ে প্রতিস্থাপন করা উচিত নয় — সিদ্ধান্তটা নির্ভর করে ক্লায়েন্টদের চাহিদার বৈচিত্র্য, ক্যাশিং-এর গুরুত্ব, ও টিমের অভিজ্ঞতার উপর।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ কেন rest_get_user() সবসময় ৮টি ফিল্ড রিটার্ন করে, এমনকি ক্লায়েন্ট শুধু নাম ও ইমেইল চাইলেও?

কারণ REST-এ একটি endpoint-এর রেসপন্স শেপ সার্ভার-সাইড কোডে ফিক্সড থাকে — rest_get_user() ফাংশনটি সবসময় একই ৮টি key দিয়ে একটি dict তৈরি করে রিটার্ন করে, ক্লায়েন্ট আসলে কী ব্যবহার করবে তা এই ফাংশনের জানার কোনো উপায় নেই। REST-এ প্রতিটি ক্লায়েন্টের ভিন্ন চাহিদার জন্য আলাদা endpoint বানানো বা query parameter দিয়ে ফিল্ড ফিল্টার করার মতো ad-hoc সমাধান লাগে, যা স্ট্যান্ডার্ড নয়।

প্র ০২ graphql_response-এ ঠিক ২টি ফিল্ড কেন এলো, ৮টির বদলে?

কারণ graphql_query["user"]["_select"]-এ শুধু "name" ও "email" আছে, এবং execute()-এর dict comprehension ({f: SCHEMA[child_type][f]["resolve"](value, {}) for f in select}) শুধু select লিস্টে থাকা নামগুলোর জন্যই resolver কল করে। বাকি ৬টা ফিল্ডের resolver-ই কল হয়নি, তাই সেগুলো graphql_response-এ থাকার প্রশ্নই আসে না।

প্র ০৩ এমন কোন পরিস্থিতিতে REST-এর ফিক্সড শেপ আসলে সুবিধা, অসুবিধা নয়?

যখন প্রায় সব ক্লায়েন্টই পুরো অবজেক্টটা চায় (over-fetching সমস্যাটাই আসলে কম), তখন REST-এর ফিক্সড শেপ ও GET রিকোয়েস্টের URL-ভিত্তিক ক্যাশিং (ব্রাউজার/CDN সরাসরি cache করতে পারে) GraphQL-এর প্রতি-রিকোয়েস্ট POST বডি-ভিত্তিক কোয়েরির (যা সাধারণত সরাসরি cache করা কঠিন) চেয়ে সহজ ও দ্রুত হয়ে ওঠে। ছোট, একক-ক্লায়েন্ট API-তে এই ট্রেড-অফ প্রায়ই REST-এর পক্ষে যায়।

অনুশীলন

  1. চিন্তা করুন: যদি ৫০টি ভিন্ন ক্লায়েন্ট থাকে, প্রত্যেকেই ইউজার অবজেক্টের ভিন্ন ২-৩টা ফিল্ড চায় (কেউ নাম+ইমেইল, কেউ ফোন+ঠিকানা), তাহলে বিশুদ্ধ REST-এ এই চাহিদাগুলো মেটাতে কী করতে হতে পারে, আর GraphQL-এ কী পরিবর্তন লাগবে?

    বিশুদ্ধ REST-এ হয় প্রতিটি ভিন্ন চাহিদার জন্য আলাদা endpoint বানাতে হবে (যেমন /users/1/contact-info, /users/1/address-only — বাস্তবে অসংখ্য endpoint হয়ে যাবে), অথবা সবসময় পুরো ৮টি ফিল্ড পাঠিয়ে over-fetch করতে হবে। GraphQL-এ কোনো সার্ভার-সাইড কোড বদলাতে হয় না — প্রতিটি ক্লায়েন্ট নিজের _select লিস্টে নিজের প্রয়োজনীয় ফিল্ড লিখে একই SCHEMA/execute() ব্যবহার করে ঠিক যা দরকার তাই পায়।

  2. পরীক্ষা করুন: উপরের কোড সেলে graphql_query["user"]["_select"]-এ "phone" ও "address"-ও যোগ করুন (মোট ৪টি ফিল্ড), Run চেপে দেখুন graphql_count ও শেষ লাইনের পার্থক্যের সংখ্যা কীভাবে বদলায়।

    _select-এ ৪টা নাম থাকলে graphql_count হবে 4, আর rest_count - graphql_count-এর ফলাফল 8 - 4 = 4 হবে (আগে ছিল 6)। এটা দেখায় GraphQL-এর সুবিধা fixed নয় — ক্লায়েন্ট যত বেশি ফিল্ড চায়, over-fetching-এর পার্থক্যও তত কমে আসে; যদি ক্লায়েন্ট আসলে সবগুলো ৮টা ফিল্ডই চাইত, তাহলে দুই পদ্ধতির ফিল্ড-সংখ্যা সমানই হতো।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
GraphQL ফান্ডামেন্টাল