06 Exercise: MVC Refactoring
January 2026 (1260 Words, 8 Minutes)
Introduction
The Model-View-Controller (MVC) pattern separates code into three components:
- Model: Data and business logic; knows nothing about the UI
- View: Displays data to the user; generates HTML/visual output
- Controller: Handles user input; coordinates Model and View
Additionally, in layered architectures, a Repository/Data Access layer handles database operations separately from the Model.
In this exercise, you will analyze “spaghetti code” that mixes all concerns together and refactor it into properly separated components.
Learning Objectives:
- Identify mixed concerns in monolithic code
- Separate code into Model, View, Controller, and Repository components
- Understand the responsibilities of each MVC component
- Design clean component interactions
Instructions:
- Study the Separation of Concerns reference
- Analyze the provided monolithic function
- Complete all four tasks
- Check your answers against the solution
Total Points: 20 Time: ~25 minutes
Reference: Separation of Concerns
| Component | Responsibility | Should NOT contain |
|---|---|---|
| Model | Business entities, calculations, rules | SQL, HTML, HTTP handling |
| View | HTML generation, formatting | Business logic, SQL |
| Controller | Request handling, coordination | Business logic, SQL, HTML |
| Repository | Database queries, persistence | Business logic, HTML |
Code to Analyze
The following function mixes data access, business logic, and presentation in a single function. Line numbers are provided for reference.
1 def show_order_details(order_id):
2 # Connect to database and fetch order
3 conn = sqlite3.connect('shop.db')
4 cursor = conn.cursor()
5 order_row = cursor.execute(
6 "SELECT * FROM orders WHERE id = ?", (order_id,)
7 ).fetchone()
8
9 if not order_row:
10 return "<h1>Order Not Found</h1>"
11
12 # Fetch order items
13 items = cursor.execute(
14 "SELECT * FROM order_items WHERE order_id = ?", (order_id,)
15 ).fetchall()
16
17 # Calculate totals (business logic)
18 subtotal = sum(item['price'] * item['quantity'] for item in items)
19 tax = subtotal * 0.19 # 19% VAT
20 shipping = 0 if subtotal > 50 else 4.99
21 total = subtotal + tax + shipping
22
23 # Check if eligible for discount
24 if order_row['customer_type'] == 'premium':
25 total = total * 0.9 # 10% premium discount
26
27 # Generate HTML
28 html = f"""
29 <div class="order">
30 <h1>Order #{order_id}</h1>
31 <table>
32 <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
33 """
34 for item in items:
35 html += f"<tr><td>{item['name']}</td><td>{item['quantity']}</td>"
36 html += f"<td>€{item['price']:.2f}</td></tr>"
37
38 html += f"""
39 </table>
40 <p>Subtotal: €{subtotal:.2f}</p>
41 <p>Tax (19%): €{tax:.2f}</p>
42 <p>Shipping: €{shipping:.2f}</p>
43 <p><strong>Total: €{total:.2f}</strong></p>
44 </div>
45 """
46 return html
Task A: Classify Code Sections (6 points)
For each section of code below, identify whether it belongs to M (Model), V (View), C (Controller), or D (Data Access/Repository).
| Lines | Classification (M/V/C/D) | Brief Reason |
|---|---|---|
| 3-7 | ||
| 9-10 | ||
| 13-15 | ||
| 17-21 | ||
| 24-25 | ||
| 27-45 |
(1 point per correct classification)
Task B: Design the Model (5 points)
Write a Python class Order that:
- Stores the order data (id, customer_type, items)
- Contains the business logic for calculating subtotal, tax, shipping, and total
- Uses
@propertydecorators for calculated values
Your Answer:
# Write your Order class here
(Grading: 1pt structure, 2pts properties, 2pts correct logic)
Task C: Design the Repository (4 points)
Write a Python class OrderRepository with a method find_by_id(order_id) that:
- Executes the database queries
- Returns an
Orderobject (orNoneif not found) - Contains NO business logic or HTML
Your Answer:
# Write your OrderRepository class here
(Grading: 2pts correct queries, 2pts proper return type)
Task D: Draw the Component Diagram (5 points)
Draw a diagram showing:
- The four components (Controller, Repository, Model, View)
- Arrows showing which component calls which
- Labels on arrows describing what is passed
Use ASCII art or simple boxes and arrows.
Your Answer:
(Draw your diagram here)
(Grading: 1pt each component shown, 1pt correct arrow directions)
Solution
Show Solution
Task A: Code Classification
| Lines | Classification | Reason |
|---|---|---|
| 3-7 | D (Data Access) | Database connection and SQL query |
| 9-10 | C (Controller) | Flow control for not-found case |
| 13-15 | D (Data Access) | SQL query for items |
| 17-21 | M (Model/Business) | Calculation logic (subtotal, tax, shipping) |
| 24-25 | M (Model/Business) | Business rule (premium discount) |
| 27-45 | V (View) | HTML generation |
Task B: Model Class
@dataclass
class OrderItem:
name: str
quantity: int
price: float
@dataclass
class Order:
id: str
customer_type: str
items: list[OrderItem]
@property
def subtotal(self) -> float:
return sum(item.price * item.quantity for item in self.items)
@property
def tax(self) -> float:
return self.subtotal * 0.19
@property
def shipping(self) -> float:
return 0 if self.subtotal > 50 else 4.99
@property
def total(self) -> float:
base_total = self.subtotal + self.tax + self.shipping
if self.customer_type == 'premium':
return base_total * 0.9
return base_total
Grading Notes:
- 1 point: Has id, customer_type, items attributes
- 1 point: Uses @property for subtotal
- 1 point: Uses @property for tax and/or shipping
- 1 point: Correct subtotal calculation
- 1 point: Correct total with premium discount logic
Task C: Repository Class
class OrderRepository:
def __init__(self, db_path: str):
self.db_path = db_path
def find_by_id(self, order_id: str) -> Order | None:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
order_row = cursor.execute(
"SELECT * FROM orders WHERE id = ?", (order_id,)
).fetchone()
if not order_row:
return None
item_rows = cursor.execute(
"SELECT * FROM order_items WHERE order_id = ?", (order_id,)
).fetchall()
items = [OrderItem(
name=row['name'],
quantity=row['quantity'],
price=row['price']
) for row in item_rows]
return Order(
id=order_id,
customer_type=order_row['customer_type'],
items=items
)
Grading Notes:
- 1 point: Correct order query
- 1 point: Correct items query
- 1 point: Returns None when not found
- 1 point: Constructs and returns Order object
Task D: Component Diagram
User Request
│
▼
┌─────────────────┐
│ Controller │ ← Coordinates flow
│ OrderController │
└────────┬────────┘
│ calls find_by_id()
▼
┌─────────────────┐
│ Repository │ ← Data Access
│ OrderRepository │
└────────┬────────┘
│ returns Order
▼
┌─────────────────┐
│ Model │ ← Business logic
│ Order │ (calculations)
└────────┬────────┘
│ passed to render()
▼
┌─────────────────┐
│ View │ ← Presentation
│ render_order() │
└────────┬────────┘
│
▼
HTML Response
Grading Notes:
- 1 point: Controller shown at top (receives request)
- 1 point: Repository shown (called by Controller)
- 1 point: Model shown (returned by Repository)
- 1 point: View shown (receives Model)
- 1 point: Correct arrow directions (downward flow)
Alternative acceptable diagram styles:
Controller → Repository → (returns) Model
↓ ↓
View ←──────────────────────┘
Or horizontal:
Request → Controller → Repository → DB
↓ ↓
View ← ← ← ← Order