2Brute2Best
🔥0
A2

Splitting a God OrderService

High-frequency interview topic Pro OnlySingle Responsibility Principle (SRP) std::print (C++23)
⚡ 60-Second Architecture SummaryA2 · Single Responsibility Principle (SRP)
Naive Bottleneck→Single Responsibility Principle (SRP) Cure
The Naive Bottleneck: Five distinct reasons to change

We dump cart validation, tax math, SQL persistence, and receipt formatting into a single `OrderService` God class. When finance changes GST rules, logistics updates delivery fee slabs, and infra modifies DB schemas in the same week, multiple squads collide in the exact same class (**Single Responsibility Principle (SRP)** violation).

A class should have one, and only one, reason to change. OrderService changes when validation rules change, tax rules change, delivery pricing changes, DB schema changes, or SMS copy changes.

The Pattern Cure: Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should encapsulate exactly one cohesive actor-facing responsibility.

We decompose OrderService into dedicated, cohesive collaborators: OrderValidator handles invariants, PriceCalculator handles tax and fee arithmetic, OrderRepository handles storage, and ReceiptFormatter handles text templating. OrderService becomes a thin coordinator whose only responsibility is orchestrating the sequence of steps.

Sprint 2 of 9 · Post-Checkout Order Processing (OrderService)
Story A — Food Delivery System — Progressive System Build
1. Where our codebase left off
← A1

In Sprint 1 (A1), we refactored Checkout to accept any PaymentStrategy (UpiPayment, CardPayment, WalletPayment). Now we have a paid PaymentReceipt and need to process the placed order.

2. What we are building now

We are building the post-payment order flow: validating cart items, calculating 5% GST and delivery fees, persisting the order to storage, and formatting the customer invoice/SMS receipt.

3. The pain point we hit

We dump cart validation, tax math, SQL persistence, and receipt formatting into a single OrderService God class. When finance changes GST rules, logistics updates delivery fee slabs, and infra modifies DB schemas in the same week, multiple squads collide in the exact same class (Single Responsibility Principle (SRP) violation).

4. What this unlocks next
Next: A3 →

With OrderValidator, PriceCalculator, OrderRepository, and ReceiptFormatter cleanly separated, our order is persisted cleanly. Next in Sprint 3 (A3 · Observer), the restaurant kitchen display, delivery rider app, and customer push service all need real-time notifications when an order is placed.

01

The product ticket

Ticket · A2FOOD-102 · Split the Monolithic OrderService God Class

Our food delivery MVP placed orders using a single OrderService::placeOrder() method. It validates cart items, calculates 5% GST and delivery fees, charges payment, saves the record to the database, and sends an SMS receipt.

Requirements
  • •

    Validate that the cart is non-empty, item prices and quantities are positive, and delivery address is provided.

  • •

    Calculate order subtotal, 5% GST, and distance-tiered delivery fee (Rs. 30 up to 5 km, Rs. 50 beyond).

  • •

    Persist the order to storage and format an SMS receipt with complete breakdown for the customer.

The next requirement that breaks naive code

Every product sprint edits OrderService. When the finance team changes GST rules, logistics updates delivery fee slabs, the database team changes table schemas, or marketing tweaks the SMS template, all four squads edit the exact same file. Merge collisions are frequent, and an SMS formatting bug recently broke order placement entirely.

02

Step 1 — The naive code & why it breaks

Everything is stuffed into one monolithic NaiveOrderService class where placeOrder() handles five distinct operational stages sequentially.

Naive design (what not to ship) · naive.cpp
#include <print>
#include <string>
#include <vector>

struct OrderItem {
    std::string name;
    int priceRupees;
    int quantity;
};

// Naive OrderService: one monolithic class handles validation, pricing, taxes,
// payments, database storage, and SMS notifications in a single placeOrder() method.
class NaiveOrderService {
    std::vector<std::string> savedOrders_;
public:
    bool placeOrder(const std::string& orderId,
                    const std::string& customerId,
                    const std::string& address,
                    double distanceKm,
                    const std::vector<OrderItem>& items,
                    const std::string& paymentMethod) {
        // 1. Validation logic
        if (items.empty()) {
            std::println("Error: Cart cannot be empty");
            return false;
        }
        if (address.empty()) {
            std::println("Error: Delivery address cannot be blank");
            return false;
        }
        for (const auto& item : items) {
            if (item.quantity <= 0 || item.priceRupees <= 0) {
                std::println("Error: Invalid item quantity or price");
                return false;
            }
        }

        // 2. Pricing & Tax calculation
        int subtotal = 0;
        for (const auto& item : items) {
            subtotal += item.priceRupees * item.quantity;
        }
        int gst = (subtotal * 5) / 100; // 5% GST
        int deliveryFee = (distanceKm <= 5.0) ? 30 : 50;
        int grandTotal = subtotal + gst + deliveryFee;

        // 3. Payment charging
        if (paymentMethod.empty()) {
            std::println("Error: Payment method missing");
            return false;
        }
        std::println("[PAYMENT] Charged Rs.{} via {}", grandTotal, paymentMethod);

        // 4. Persistence / Database saving
        std::string record = std::format("ORDER#{}|CUST#{}|AMT#{}", orderId, customerId, grandTotal);
        savedOrders_.push_back(record);
        std::println("[DB] Saved order record: {}", record);

        // 5. Receipt & SMS notification
        std::string sms = std::format("Order {} placed! Subtotal: Rs.{}, GST: Rs.{}, Delivery: Rs.{}. Total: Rs.{}. Delivering to: {}",
                                      orderId, subtotal, gst, deliveryFee, grandTotal, address);
        std::println("[SMS] Sent to customer {}: {}", customerId, sms);

        return true;
    }
};

int main() {
    NaiveOrderService service;
    std::vector<OrderItem> cart = {
        {"Paneer Butter Masala", 280, 1},
        {"Garlic Naan", 40, 2}
    };
    service.placeOrder("ORD-201", "CUST-42", "Flat 302, Green Glen Layout, Bengaluru", 4.2, cart, "UPI");
}
Five distinct reasons to change

A class should have one, and only one, reason to change. OrderService changes when validation rules change, tax rules change, delivery pricing changes, DB schema changes, or SMS copy changes.

High blast radius and frequent merge conflicts

Engineers from Finance, Logistics, and Core Checkout collide in Git on the same file. A typo in string formatting or tax math can crash core order persistence.

Untestable in isolation

You cannot test whether delivery fee tiers work at 4.9 km vs 5.1 km without supplying valid customer addresses, triggering fake payment charges, and printing fake SMS messages.

03

Step 2 — The fix: Single Responsibility Principle (SRP)

Core idea in one sentence

A class should have only one reason to change, meaning it should encapsulate exactly one cohesive actor-facing responsibility.

We decompose OrderService into dedicated, cohesive collaborators: OrderValidator handles invariants, PriceCalculator handles tax and fee arithmetic, OrderRepository handles storage, and ReceiptFormatter handles text templating. OrderService becomes a thin coordinator whose only responsibility is orchestrating the sequence of steps.

Pattern roles in our story
Validator

OrderValidator — checks cart emptiness, address presence, and item non-negativity.

Calculator

PriceCalculator — calculates subtotal, 5% GST, and distance-based delivery fees.

Repository

OrderRepository — manages persistence and order retrieval by ID.

Formatter

ReceiptFormatter — constructs human-readable SMS and receipt summaries.

Coordinator

OrderService — coordinates the flow between validator, calculator, repository, and formatter.

Brute2Best Pro Content

Unlock Full UML Diagram & C++23 Solution

The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers