Home

06 Exercise: MVC Refactoring

exercises chapter-06 architecture mvc refactoring separation-of-concerns

Introduction

The Model-View-Controller (MVC) pattern separates code into three components:

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:

Instructions:

  1. Study the Separation of Concerns reference
  2. Analyze the provided monolithic function
  3. Complete all four tasks
  4. 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:

  1. Stores the order data (id, customer_type, items)
  2. Contains the business logic for calculating subtotal, tax, shipping, and total
  3. Uses @property decorators 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:

  1. Executes the database queries
  2. Returns an Order object (or None if not found)
  3. 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:

  1. The four components (Controller, Repository, Model, View)
  2. Arrows showing which component calls which
  3. 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
© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk