REST রিসোর্স মডেলিং
এই পাঠে যা শিখবেন
- কেন REST URL-এ verb না লিখে শুধু noun (রিসোর্সের নাম) লেখা হয়
- রিসোর্স হায়ারার্কি কীভাবে সম্পর্ক (parent-child) প্রকাশ করে —
/users/:id/posts - Python-এ একটি নেস্টেড dict "ডেটাবেস" মডেল করা এবং পাথ-ভিত্তিক অ্যাক্সেস ফাংশন লেখা
- একই ডেটার উপর দুই ধরনের কুয়েরি — "সব পোস্ট" বনাম "একজন ইউজারের পোস্ট" — বাস্তবে চালিয়ে যাচাই করা
১ · Noun, verb নয় — URL-এ কী থাকা উচিত
REST API ডিজাইনের সবচেয়ে সাধারণ ভুল হলো URL-এ ক্রিয়া (verb) বসিয়ে দেওয়া — যেমন
/getUser, /createPost, /deleteComment। এটি ভুল কারণ
HTTP মেথডই ইতিমধ্যে বলে দেয় কী করা হচ্ছে (GET = পড়া,
POST = তৈরি করা, DELETE = মুছে ফেলা) — URL-এর কাজ শুধু
কোন রিসোর্স নিয়ে কাজ হচ্ছে তা প্রকাশ করা।
GET /getUser?id=5, POST /createUser, POST /deleteUser?id=5 — verb URL-এও আছে, মেথডেও আছে, অপ্রয়োজনীয় পুনরাবৃত্তি।GET /users/5, POST /users, DELETE /users/5 — URL শুধু রিসোর্স বলে, মেথড কাজ বলে।/users, /posts, /orders) — কখনো ক্রিয়াপদ দিয়ে নয়।
২ · রিসোর্স হায়ারার্কি — সম্পর্ক প্রকাশ করা
বাস্তব ডেটায় রিসোর্সগুলোর মধ্যে সম্পর্ক থাকে — একজন ইউজারের একাধিক পোস্ট থাকতে পারে। এই সম্পর্ক URL পাথেই প্রকাশ করা যায়, নেস্টেড (nested) রিসোর্স হিসেবে।
লক্ষণীয়, /posts (ফ্ল্যাট, সব পোস্ট) এবং /users/:id/posts (নেস্টেড, শুধু একজনের
পোস্ট) — দুটো সম্পূর্ণ ভিন্ন কুয়েরি, যদিও দুটোই "পোস্ট" রিসোর্স নিয়েই কথা বলছে। নিচের কোড সেলে দুটোই বাস্তবে
বাস্তবায়ন করে দেখানো হচ্ছে।
৩ · Python-এ নেস্টেড রিসোর্স ডেটাবেস ও পাথ-ভিত্তিক অ্যাক্সেস
নিচের কোড সেলে একটি ছোট্ট ইন-মেমরি "ডেটাবেস" তৈরি করা হচ্ছে — users ডিকশনারিতে প্রতিটি ইউজারের
নিজের পোস্টের আইডি তালিকা আছে, এবং posts ডিকশনারিতে সব পোস্টের পূর্ণ ডেটা। এরপর একটি ছোট্ট
পাথ-ম্যাচার ফাংশন লেখা হচ্ছে যা /posts এবং /users/:id/posts — দুটো ভিন্ন পাথ প্যাটার্ন
চিনে সঠিক ফাংশন কল করে (L01-এর Router ধারণার একটি ছোট সংস্করণ)।
# নেস্টেড রিসোর্স "ডেটাবেস" -- users প্রতিটি নিজের post_ids রাখে
users = {
1: {"id": 1, "name": "রহিম", "post_ids": [101, 102]},
2: {"id": 2, "name": "করিম", "post_ids": [103]},
}
posts = {
101: {"id": 101, "user_id": 1, "title": "আমার প্রথম পোস্ট"},
102: {"id": 102, "user_id": 1, "title": "Python শেখার অভিজ্ঞতা"},
103: {"id": 103, "user_id": 2, "title": "করিমের ভ্রমণকাহিনি"},
}
def list_all_posts():
"""GET /posts -- সব পোস্ট, ফ্ল্যাট কালেকশন"""
return list(posts.values())
def list_user_posts(user_id):
"""GET /users/:id/posts -- শুধু নির্দিষ্ট একজন ইউজারের পোস্ট"""
if user_id not in users:
return None
return [posts[pid] for pid in users[user_id]["post_ids"]]
def handle_request(path):
"""ছোট্ট পাথ-ম্যাচার -- /posts এবং /users/:id/posts দুটো প্যাটার্নই চেনে"""
segments = [s for s in path.strip('/').split('/') if s]
if segments == ['posts']:
return 200, list_all_posts()
if len(segments) == 3 and segments[0] == 'users' and segments[2] == 'posts':
try:
user_id = int(segments[1])
except ValueError:
return 400, {"error": "user id সংখ্যা হতে হবে"}
result = list_user_posts(user_id)
if result is None:
return 404, {"error": "ইউজার পাওয়া যায়নি"}
return 200, result
return 404, {"error": "রুট পাওয়া যায়নি"}
# --- সব পোস্ট বনাম একজন ইউজারের পোস্ট ---
status_a, body_a = handle_request('/posts')
print(f"GET /posts -> status {status_a}, মোট {len(body_a)}টি পোস্ট")
for p in body_a:
print(f" #{p['id']} (user {p['user_id']}): {p['title']}")
print()
status_b, body_b = handle_request('/users/1/posts')
print(f"GET /users/1/posts -> status {status_b}, রহিমের {len(body_b)}টি পোস্ট")
for p in body_b:
print(f" #{p['id']}: {p['title']}")
print()
status_c, body_c = handle_request('/users/2/posts')
print(f"GET /users/2/posts -> status {status_c}, করিমের {len(body_c)}টি পোস্ট")
for p in body_c:
print(f" #{p['id']}: {p['title']}")
print()
status_d, body_d = handle_request('/users/99/posts')
print(f"GET /users/99/posts -> status {status_d}, বডি: {body_d}")
print(f"\nযাচাই: সব পোস্টের সংখ্যা ({len(body_a)}) == রহিমের ({len(body_b)}) + করিমের ({len(body_c)}) পোস্ট -> "
f"{len(body_a) == len(body_b) + len(body_c)}")
/posts ৩টি পোস্ট রিটার্ন করে (৩টিই), /users/1/posts ঠিক ২টি (শুধু রহিমের ১০১, ১০২),
আর /users/2/posts ঠিক ১টি (শুধু করিমের ১০৩) — একই posts ডেটা থেকে, শুধু হায়ারার্কি
URL অনুযায়ী ভিন্নভাবে ফিল্টার হচ্ছে। /users/99/posts-এ ৯৯ নম্বর ইউজার নেই বলে 404
আসে — handle_request() কখনো crash করে না, প্রতিটি অবস্থার জন্য একটি সঠিক status code রিটার্ন করে।
REST resource modeling মানে দুটো সিদ্ধান্ত — URL-এ verb না বসিয়ে noun বসানো (মেথড কাজ বলে দেয়), এবং
parent-child সম্পর্ক থাকলে নেস্টেড পাথ ব্যবহার করা (/users/:id/posts)। এই পাঠের
handle_request() ফাংশনটিই M6-এর পরের পাঠগুলোর ভিত্তি — L24-এ এই একই ধরনের ডেটাবেসে
সঠিক HTTP verb ও status code যোগ করা হবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
/users/deleteUser/5-এর বদলে DELETE /users/5 ব্যবহার করা কেন ভালো?
প্রথমটিতে "delete" কাজটি URL-এর মধ্যে লেখা আছে, যা GET /users/deleteUser/5 দিয়ে ভুলবশত
কল হলেও একই কাজ করবে বলে মনে হতে পারে (URL দেখেই বোঝা যায় না আসলে কোন HTTP মেথড ব্যবহার হচ্ছে)।
দ্বিতীয়টিতে URL শুধু রিসোর্স (/users/5) বলে, আর DELETE মেথডই কাজটি নির্ধারণ
করে — তাই ভুল মেথডে কল হলে সহজেই ধরা পড়ে (যেমন GET /users/5 শুধু পড়বে, মুছবে না)।
প্র ০২
একটি পোস্টের কমেন্টগুলো কি /comments?post_id=101 নাকি /posts/101/comments — কোনটি ভালো ডিজাইন?
/posts/101/comments ভালো, কারণ এটি হায়ারার্কি সম্পর্কটি URL structure-এই স্পষ্টভাবে
প্রকাশ করে — যে কেউ পাথ দেখেই বুঝবে এটি "পোস্ট ১০১-এর কমেন্টসমূহ"। কোয়েরি প্যারামিটার (?post_id=101)
ব্যবহার করা যেতে পারে ঐচ্ছিক ফিল্টারিং/সার্চের জন্য (যেমন L25-এর ফিল্টারিং), কিন্তু মূল
parent-child সম্পর্ক প্রকাশের জন্য নেস্টেড পাথই প্রচলিত ও স্পষ্ট।
প্র ০৩
উপরের কোডে handle_request('/users/1/posts') কেন ঠিক ২টি পোস্ট রিটার্ন করে, ৩টি নয়?
কারণ list_user_posts(1) শুধুমাত্র users[1]["post_ids"]-এ থাকা আইডিগুলো
([101, 102]) থেকে পোস্ট বের করে আনে — posts ডিকশনারিতে থাকা ১০৩ নম্বর পোস্টটি
ইউজার ২ (করিম)-এর, তাই সেটি এই তালিকায় নেই। এটাই হায়ারার্কি-ভিত্তিক ফিল্টারিং-এর মূল কাজ — শুধু সংশ্লিষ্ট
চাইল্ড রিসোর্স ফেরত দেওয়া, পুরো কালেকশন নয়।
অনুশীলন
-
চিন্তা করুন: একটি ই-কমার্স সাইটে একটি "অর্ডার"-এর "অর্ডার আইটেম"গুলোর জন্য আপনি কোন URL
পাথ ডিজাইন করবেন, এবং কেন?
/orders/:id/items— কারণ একটি অর্ডার-আইটেম সবসময় একটি নির্দিষ্ট অর্ডারের অংশ (parent-child সম্পর্ক), এবং এই নেস্টেড পাথ সেই মালিকানা সম্পর্কটি সরাসরি প্রকাশ করে, ঠিক যেমন এই পাঠে/users/:id/postsইউজার-পোস্ট সম্পর্ক প্রকাশ করেছে। -
পরীক্ষা করুন: উপরের কোড সেলে
usersডিকশনারিতে একটি নতুন ইউজার3: {"id": 3, "name": "সালমা", "post_ids": []}যোগ করুন, তারপরhandle_request('/users/3/posts')কল করে দেখুন এটি কী রিটার্ন করে।এটি
status 200এবং একটি খালি লিস্ট[]রিটার্ন করবে —404নয়, কারণ সালমাusers-এ আছে (তাই "ইউজার পাওয়া যায়নি" ভুল হবে), শুধু তার কোনো পোস্ট নেই। এটি একটি গুরুত্বপূর্ণ পার্থক্য — "রিসোর্স আছে কিন্তু খালি" আর "রিসোর্স নেই" দুটো ভিন্ন অবস্থা, এবং সঠিক REST ডিজাইনে এই দুটোর জন্য ভিন্ন status code হওয়া উচিত (যা L24-এ বিস্তারিত শেখানো হবে)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
- Python Programming কোর্স সহোদর কোর্স এই পাঠের dict-ভিত্তিক ডেটা মডেলিং ও list comprehension-এর ভাষাগত ভিত্তি সেই কোর্সে তৈরি হয়েছে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।