CodingBowl

10 Python Patterns for Scalable Django Projects, Part 3: Builder Pattern

Published on 22 Jan 2026 Tech Software Architecture
image
Photo by Alex Zaj on Unsplash

As Django applications grow, object creation often becomes complex. Models, services, and view logic start sharing responsibility for assembling data. The Builder Pattern solves this by separating how an object is constructed from how it is used, keeping code readable, testable, and flexible.

What Problem the Builder Pattern Solves

When creating complex objects with optional fields, defaults, and validation rules, constructors and views quickly become cluttered. The Builder Pattern centralizes construction logic into a dedicated builder.

The Anti-Pattern


class CreateOrderView(View):
    def post(self, request):
        order = Order(
            customer_id=request.POST.get("customer_id"),
            shipping_address=request.POST.get("shipping_address"),
            discount_code=request.POST.get("discount_code"),
            is_expedited=bool(request.POST.get("expedited")),
            created_by=request.user,
        )
        order.calculate_totals()
        order.save()

The view is responsible for assembling, validating, and finalizing the object.

Step 1: Define the Builder Interface


class OrderBuilder:
    def set_customer(self, customer):
        raise NotImplementedError

    def set_shipping(self, address):
        raise NotImplementedError

    def apply_discount(self, code):
        raise NotImplementedError

    def set_expedited(self, value):
        raise NotImplementedError

    def build(self):
        raise NotImplementedError

Step 2: Implement a Concrete Builder


from .models import Order

class DjangoOrderBuilder(OrderBuilder):
    def __init__(self):
        self.order = Order()

    def set_customer(self, customer):
        self.order.customer = customer
        return self

    def set_shipping(self, address):
        self.order.shipping_address = address
        return self

    def apply_discount(self, code):
        if code:
            self.order.discount_code = code
        return self

    def set_expedited(self, value):
        self.order.is_expedited = value
        return self

    def build(self):
        self.order.calculate_totals()
        self.order.save()
        return self.order

The builder exposes a fluent API and owns all construction logic.

Step 3: Use the Builder in a Service


class OrderCreationService:
    def __init__(self, builder):
        self.builder = builder

    def create(self, data, user):
        return (
            self.builder
            .set_customer(data["customer"])
            .set_shipping(data["shipping_address"])
            .apply_discount(data.get("discount_code"))
            .set_expedited(data.get("expedited", False))
            .build()
        )

The service orchestrates the steps without knowing implementation details.

Step 4: Use the Builder in a Class-Based View


from django.views import View
from django.http import JsonResponse
from .builders import DjangoOrderBuilder
from .services import OrderCreationService

class CreateOrderView(View):
    builder_class = DjangoOrderBuilder
    service_class = OrderCreationService

    def post(self, request):
        builder = self.builder_class()
        service = self.service_class(builder)

        order = service.create(
            data=request.POST,
            user=request.user,
        )

        return JsonResponse({
            "order_id": order.id,
            "status": order.status,
        })

Step 5: Testing the Builder Independently


class FakeOrderBuilder:
    def __init__(self):
        self.called = []

    def set_customer(self, customer):
        self.called.append("customer")
        return self

    def set_shipping(self, address):
        self.called.append("shipping")
        return self

    def apply_discount(self, code):
        self.called.append("discount")
        return self

    def set_expedited(self, value):
        self.called.append("expedited")
        return self

    def build(self):
        return "order"

def test_order_creation_flow():
    builder = FakeOrderBuilder()
    service = OrderCreationService(builder)

    result = service.create(
        data={
            "customer": 1,
            "shipping_address": "SG",
            "discount_code": None,
        },
        user=None,
    )

    assert result == "order"
    assert "customer" in builder.called

Why Builder Works Well in Django

  • Keeps object creation out of views
  • Encapsulates defaults and invariants
  • Supports complex, optional construction flows
  • Improves testability and reuse

Architectural Insight

If constructing an object requires more than a few parameters, or involves conditional logic and side effects, it is no longer a constructor problem. The Builder Pattern makes object creation explicit and intentional.

Meow! AI Assistance Note

This post was created with the assistance of Gemini AI and ChatGPT.
It is shared for informational purposes only and is not intended to mislead, cause harm, or misrepresent facts. While efforts have been made to ensure accuracy, readers are encouraged to verify information independently. Portions of the content may not be entirely original.

image
Photo by Yibo Wei on Unsplash