পাঠ ০৫ · ৫৮-এর মধ্যে · মডিউল ২
Home / Courses / Full-Stack Web Frameworks / MVC আর্কিটেকচার প্যাটার্ন

MVC আর্কিটেকচার প্যাটার্ন

The MVC (Model-View-Controller) architecture pattern
৯ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • MVC-এর তিনটি অংশের প্রতিটির সঠিক দায়িত্ব ও সীমারেখা
  • একটি রিকোয়েস্ট MVC-তে ঠিক কোন ক্রমে প্রবাহিত হয় (Controller → Model → View → রেসপন্স)
  • কেন এই আলাদাকরণ (separation of concerns) কোড মেইনটেইন করা সহজ করে
  • Python দিয়ে একটি সম্পূর্ণ, সত্যিকারের কার্যকর Todo MVC ত্রয়ী — Model, View, Controller তিনটিই আসল ক্লাস হিসেবে

১ · MVC-এর তিনটি অংশ

MVCModel-View-Controllerএকটি আর্কিটেকচার প্যাটার্ন যা অ্যাপ্লিকেশনকে ডেটা, প্রেজেন্টেশন, ও অর্কেস্ট্রেশন — তিনটি আলাদা দায়িত্বে ভাগ করে। হলো ওয়েব অ্যাপ্লিকেশনের সবচেয়ে পুরনো ও ব্যাপকভাবে ব্যবহৃত আর্কিটেকচার প্যাটার্নগুলোর একটি। এটি একটি অ্যাপ্লিকেশনের কোডকে তিনটি স্পষ্ট দায়িত্বে ভাগ করে, যাতে প্রতিটি অংশ শুধু নিজের কাজেই মনোযোগ দেয়।

Model
ডেটা ও বিজনেস লজিক ধারণ করে — কীভাবে ডেটা তৈরি, পড়া, পরিবর্তন হয়। View বা Controller সম্পর্কে কিছুই জানে না।
View
ডেটা কীভাবে ব্যবহারকারীর কাছে উপস্থাপন করা হবে তা ঠিক করে। শুধু দেওয়া ডেটা রেন্ডার করে — নিজে ডেটা আনে না বা বদলায় না।
Controller
রিকোয়েস্ট গ্রহণ করে, প্রয়োজনমতো Model-কে কল করে, ফলাফল View-কে দিয়ে রেন্ডার করায়, ও চূড়ান্ত রেসপন্স ফেরত দেয়।
রিকোয়েস্ট (action + params) Controller রিকোয়েস্ট গ্রহণ ও অর্কেস্ট্রেশন Model ডেটা পড়া/লেখা View রেন্ডার/ফরম্যাট রেসপন্স Model কখনো View জানে না, View কখনো Model সরাসরি বদলায় না
Controller রিকোয়েস্ট গ্রহণ করে Model থেকে ডেটা আনে, তারপর সেই ডেটা View-কে দিয়ে রেন্ডার করায়, এবং চূড়ান্ত রেসপন্স ফেরত পাঠায়।
Router প্যাটার্নের সাথে সম্পর্ক

L01-এ দেখা Router ক্লাসটি একটি রিকোয়েস্টের পাথকে একটি হ্যান্ডলার ফাংশনে ম্যাপ করেছিল। MVC-তে সেই হ্যান্ডলার ফাংশনটিই হলো Controller-এর একটি মেথড — Router শুধু ঠিক করে কোন Controller অ্যাকশন কল হবে, Controller নিজে Model ও View সমন্বয় করার কাজটি করে।

২ · সম্পূর্ণ MVC ত্রয়ী — একটি Todo রিসোর্স

নিচের কোড সেলে একটি সম্পূর্ণ, সত্যিকারের কার্যকর MVC ত্রয়ী তৈরি করা হয়েছে Todo রিসোর্সের জন্য — TodoModel ডেটা ধারণ করে, TodoView সেই ডেটা টেক্সট আকারে ফরম্যাট করে, এবং TodoController রিকোয়েস্ট গ্রহণ করে দুটোকে সমন্বয় করে। ছয়টি ভিন্ন রিকোয়েস্ট পাঠিয়ে পুরো প্রবাহ ট্রেস করা হয়েছে।

Python
# ---------- MODEL -- শুধু ডেটা ও ডেটা-সম্পর্কিত লজিক ----------
class TodoModel:
    def __init__(self):
        self._todos = []
        self._next_id = 1

    def add(self, title):
        todo = {"id": self._next_id, "title": title, "done": False}
        self._todos.append(todo)
        self._next_id += 1
        return todo

    def toggle_done(self, todo_id):
        for todo in self._todos:
            if todo["id"] == todo_id:
                todo["done"] = not todo["done"]
                return todo
        return None

    def all(self):
        return list(self._todos)


# ---------- VIEW -- শুধু প্রেজেন্টেশন, নিজে ডেটা আনে না ----------
class TodoView:
    @staticmethod
    def render_list(todos):
        if not todos:
            return "(কোনো টুডু নেই)"
        lines = []
        for t in todos:
            mark = "[x]" if t["done"] else "[ ]"
            lines.append(f"{mark} #{t['id']} {t['title']}")
        return "\n".join(lines)

    @staticmethod
    def render_single(todo):
        if todo is None:
            return "404 Not Found -- এই টুডু নেই"
        mark = "সম্পন্ন" if todo["done"] else "বাকি আছে"
        return f"টুডু #{todo['id']}: {todo['title']} -- {mark}"


# ---------- CONTROLLER -- রিকোয়েস্ট গ্রহণ ও Model/View সমন্বয় ----------
class TodoController:
    def __init__(self, model, view):
        self.model = model
        self.view = view

    def handle_request(self, action, params=None):
        params = params or {}
        if action == "add_todo":
            todo = self.model.add(params["title"])
            return self.view.render_single(todo)
        elif action == "toggle_todo":
            todo = self.model.toggle_done(params["id"])
            return self.view.render_single(todo)
        elif action == "list_todos":
            todos = self.model.all()
            return self.view.render_list(todos)
        else:
            return "404 Not Found"


model = TodoModel()
view = TodoView()
controller = TodoController(model, view)

print("== রিকোয়েস্ট ১: add_todo ==")
print(controller.handle_request("add_todo", {"title": "দুধ কেনা"}))

print("\n== রিকোয়েস্ট ২: add_todo ==")
print(controller.handle_request("add_todo", {"title": "রিপোর্ট জমা দেওয়া"}))

print("\n== রিকোয়েস্ট ৩: list_todos ==")
print(controller.handle_request("list_todos"))

print("\n== রিকোয়েস্ট ৪: toggle_todo id=1 ==")
print(controller.handle_request("toggle_todo", {"id": 1}))

print("\n== রিকোয়েস্ট ৫: list_todos (আবার) ==")
print(controller.handle_request("list_todos"))

print("\n== রিকোয়েস্ট ৬: অজানা অ্যাকশন ==")
print(controller.handle_request("delete_everything"))

    
লক্ষ্য করুন TodoModel ক্লাসের ভেতর কোথাও TodoView-এর কোনো উল্লেখ নেই, এবং TodoView-এর মেথডগুলো শুধু যা প্যারামিটার হিসেবে দেওয়া হয় তাই রেন্ডার করে — নিজে model._todos স্পর্শ করে না। রিকোয়েস্ট ৪-এ toggle_todo কল করার পর রিকোয়েস্ট ৫-এর list_todos-এ #1 টুডুর মার্ক [ ] থেকে [x]-এ বদলে যায় — কারণ Controller প্রতিবারই Model থেকে সর্বশেষ ডেটা তাজা করে আনে।

৩ · কেন এই আলাদাকরণ গুরুত্বপূর্ণ

MVC-তে প্রতিটি অংশ স্বাধীনভাবে বদলানো যায় — Controller ও Model অপরিবর্তিত রেখে TodoView-এর আউটপুট ফরম্যাট (যেমন প্লেইন টেক্সটের বদলে HTML বা JSON) বদলে ফেলা যায়, কারণ View শুধু ডেটা গ্রহণ করে ও ফরম্যাট করে — কীভাবে সেই ডেটা তৈরি হলো তা নিয়ে মাথা ঘামায় না। একইভাবে Model-এর ভেতরের স্টোরেজ (এখানে একটি পাইথন লিস্ট, বাস্তবে একটি ডেটাবেস টেবিল হতে পারে) বদলে ফেললেও Controller বা View-এর কোনো কোড বদলাতে হয় না, যতক্ষণ Model-এর পাবলিক মেথডগুলোর (add, toggle_done, all) স্বাক্ষর একই থাকে।

মূল কথা · Key takeaway

MVC একটি অ্যাপ্লিকেশনকে তিনটি স্পষ্ট দায়িত্বে ভাগ করে — Model (ডেটা), View (প্রেজেন্টেশন), Controller (অর্কেস্ট্রেশন)। একটি রিকোয়েস্ট সবসময় Controller দিয়ে শুরু হয়, যেটি Model থেকে ডেটা এনে View-কে দিয়ে রেন্ডার করায় ও চূড়ান্ত রেসপন্স ফেরত দেয়। এই আলাদাকরণের ফলে প্রতিটি অংশ স্বাধীনভাবে পরীক্ষা ও পরিবর্তন করা যায় — পরের পাঠে (L06) আমরা দেখব MVVM ও MVP কীভাবে এই একই সমস্যার ভিন্ন সমাধান দেয়।

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

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

প্র ০১ উপরের কোডে TodoModel কেন কখনো সরাসরি প্রিন্ট বা কোনো টেক্সট ফরম্যাট করে না?

কারণ প্রেজেন্টেশন (কীভাবে দেখানো হবে) Model-এর দায়িত্ব নয় — সেটা View-এর কাজ। Model যদি নিজেই টেক্সট ফরম্যাট করত, তাহলে ভবিষ্যতে একই ডেটা JSON বা HTML আকারে দেখাতে চাইলে Model-এর কোড বদলাতে হতো, যা আলাদাকরণের মূল লক্ষ্যকে ভেঙে দেয়। Model শুধু কাঁচা ডেটা (dict) ফেরত দেয়, View সিদ্ধান্ত নেয় সেটা কীভাবে উপস্থাপিত হবে।

প্র ০২ TodoController.handle_request-এ কেন Model বা View-এর ভেতরের কোনো বিস্তারিত (যেমন _todos লিস্ট) সরাসরি স্পর্শ করা হয় না?

Controller শুধু Model ও View-এর পাবলিক মেথড কল করে (model.add(...), view.render_list(...)) — তাদের ভেতরের ডেটা স্ট্রাকচার সম্পর্কে কিছু জানে না বা জানার প্রয়োজন নেই। এভাবে Model-এর ভেতরের স্টোরেজ (লিস্ট থেকে ডেটাবেস টেবিলে) বদলে ফেললেও Controller-এর কোনো কোড বদলাতে হয় না, যতক্ষণ মেথডের স্বাক্ষর একই থাকে।

প্র ০৩ রিকোয়েস্ট ৬-এ "delete_everything" অ্যাকশন কেন "404 Not Found" ফেরত দেয়, অথচ Model বা View-এ কোনো এরর হ্যান্ডলিং কোড নেই?

কারণ "delete_everything" নামে কোনো if/elif শাখা handle_request-এ নেই, তাই এক্সিকিউশন সরাসরি শেষের else-এ পড়ে, যা "404 Not Found" ফেরত দেয় — এই ক্ষেত্রে Model বা View-কে একদমই কল করা হয় না। এটাই দেখায় Controller-ই একমাত্র জায়গা যেখানে "কোন রিকোয়েস্ট বৈধ" এই সিদ্ধান্ত নেওয়া হয়।

অনুশীলন

  1. চিন্তা করুন: যদি TodoView.render_list-এর ভেতর সরাসরি model.all() কল করা হতো (Controller-এর মাধ্যমে ডেটা না পাঠিয়ে), তাহলে MVC-এর কোন নিয়মটি ভাঙা হতো, এবং এর ফলে কী সমস্যা হতে পারে?

    এতে View সরাসরি Model-এর উপর নির্ভরশীল হয়ে যেত, যা "View কখনো নিজে ডেটা আনে না" নিয়মটি ভাঙে। ফলে View-কে আলাদাভাবে টেস্ট করা কঠিন হয়ে যেত (সবসময় একটি আসল Model দরকার হতো), এবং একই View বিভিন্ন উৎসের ডেটা দিয়ে (যেমন টেস্টের জন্য ভুয়া ডেটা) ব্যবহার করা যেত না — Controller-ই একমাত্র জায়গা হওয়া উচিত যেখানে Model ও View একসাথে সংযুক্ত হয়।

  2. পরীক্ষা করুন: উপরের কোড সেলে TodoController-এ একটি নতুন অ্যাকশন "delete_todo" যোগ করুন যা self.model._todos থেকে নির্দিষ্ট id-র টুডু সরিয়ে দেয় (Model-এ একটি নতুন delete মেথড লিখে), তারপর list_todos আবার কল করে দেখুন টুডুটি সত্যিই বাদ পড়েছে কি না।

    TodoModel-এ def delete(self, todo_id): self._todos = [t for t in self._todos if t["id"] != todo_id] যোগ করে, TodoController-এ elif action == "delete_todo": self.model.delete(params["id"]); return self.view.render_list(self.model.all()) যোগ করলে delete_todo কলের পর পরবর্তী list_todos-এর আউটপুটে সেই টুডুটি আর দেখাবে না — এটাই দেখায় নতুন ফিচার যোগ করতে শুধু Model ও Controller-এ পরিবর্তন লাগে, View অপরিবর্তিত থাকে।

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

  • পরবর্তী পাঠ L06 MVVM ও MVP প্যাটার্ন — MVC-এর দুটি বিকল্প ও তাদের ভিন্ন সমাধান দেখুন।
  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
  • Python Programming কোর্স সহোদর কোর্স উপরের কোড সেলে ব্যবহৃত ক্লাস, ডিকশনারি ও লিস্ট কমপ্রিহেনশনের ভাষাগত ভিত্তি সেই কোর্সে শেখানো হয়েছে।
  • সব 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 — সব এক জায়গায়।
আগের পাঠ
মনোলিথ বনাম মাইক্রোসার্ভিস বনাম SPA/SSR/SSG — একটি আর্কিটেকচার ম্যাপ