2Brute2Best
πŸ”₯0
A6

Swapping DB & Payment Gateway in Tests

High-frequency interview topic Pro OnlyDependency Inversion Principle (DIP)Constructor Injection
⚑ 60-Second Architecture SummaryA6 · Dependency Inversion Principle (DIP) + Constructor Injection
Naive Bottleneck→Dependency Inversion Principle (DIP) + Constructor Injection Cure
The Naive Bottleneck: Unit tests require live databases and network connections

`OrderService` directly instantiates concrete `PostgresOrderRepo` and `RazorpayGateway` inside its constructor. Because domain policy is tightly coupled to low-level SQL drivers and third-party HTTP SDKs, unit tests require live PostgreSQL databases and internet credentials, running slowly and flaking in CI (**Dependency Inversion Principle (DIP)** violation).

Because OrderService hardcodes PostgresOrderRepo and RazorpayGateway, tests cannot run in isolation. A flaky network or downed staging database breaks the entire CI pipeline.

The Pattern Cure: Dependency Inversion Principle (DIP) + Constructor Injection

High-level modules must not depend on low-level modules; both must depend on abstractions.

Instead of OrderService creating concrete dependencies, we define two pure virtual interfaces: OrderRepository and PaymentGateway. OrderService depends only on these abstract contracts, injected through its constructor. In production, we inject PostgresOrderRepo and RazorpayGateway; in unit tests, we inject InMemoryOrderRepo and FakePaymentGateway.

Sprint 6 of 9 Β· Decoupling Infrastructure For Fast Unit Testing
Story A β€” Food Delivery System β€” Progressive System Build
1. Where our codebase left off
← A5

In Sprint 5 (A5), we decoupled vehicle instantiation with Factory Method so FulfillmentService dispatches delivery partners through abstract interfaces.

2. What we are building now

We need to make our core OrderService enterprise-ready and thoroughly testable by running fast, deterministic unit test suites in CI without depending on live cloud infrastructure.

3. The pain point we hit

OrderService directly instantiates concrete PostgresOrderRepo and RazorpayGateway inside its constructor. Because domain policy is tightly coupled to low-level SQL drivers and third-party HTTP SDKs, unit tests require live PostgreSQL databases and internet credentials, running slowly and flaking in CI (Dependency Inversion Principle (DIP) violation).

4. What this unlocks next
Next: A7 β†’

By inverting dependencies with pure virtual interfaces (OrderRepository, PaymentGateway) and constructor injection, production injects PostgreSQL and Razorpay while unit tests inject lightning-fast in-memory test doubles. Next in Sprint 7 (A7 Β· LSP & ISP), our expanding partner ecosystem breaks down when new cloud kitchens and dine-in partners are forced to implement bloated fat interfaces with dummy 'Not supported!' exceptions.

01

The product ticket

Ticket Β· A6FOOD-106 Β· Swap Database and Payment Gateway in Unit Tests

Our OrderService directly instantiates PostgresOrderRepo and RazorpayGateway inside its constructor. As a result, running unit tests requires a live PostgreSQL instance on port 5432 and live internet credentials for Razorpay, causing test runs to take seconds and fail when offline.

Requirements
  • β€’

    Decouple OrderService from concrete database drivers and network payment SDKs.

  • β€’

    Enable 100% in-memory unit tests that execute in microseconds without starting PostgreSQL or calling Razorpay.

  • β€’

    Allow swapping infrastructure backends (e.g. PostgreSQL in production, hash map in tests, DynamoDB for regional expansion) without modifying order processing logic.

The next requirement that breaks naive code

Next month we are launching in Southeast Asia with local payment rails (GrabPay, GoPay) and AWS DynamoDB. The core checkout rules must remain completely untouched when switching storage engines or payment providers.

02

Step 1 β€” The naive code & why it breaks

The initial implementation instantiates concrete PostgresOrderRepo and RazorpayGateway objects directly inside OrderService.

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

struct Order {
    std::string id;
    std::string customerId;
    int amountRupees;
};

// Low-level database detail directly coupled to Postgres
class PostgresOrderRepo {
public:
    void save(const Order& order) {
        std::println("[DB:Postgres] Connecting to postgres://prod-db:5432/orders... Saved order {}", order.id);
    }

    std::optional<Order> findById(const std::string& orderId) {
        std::println("[DB:Postgres] SELECT * FROM orders WHERE id = '{}'", orderId);
        return Order{orderId, "cust_live", 450};
    }
};

// Low-level network detail directly coupled to Razorpay HTTP API
class RazorpayGateway {
public:
    bool charge(const std::string& customerId, int amountRupees) {
        std::println("[GATEWAY:Razorpay] POST https://api.razorpay.com/v1/charges for {} (Rs.{})",
                     customerId, amountRupees);
        return true;
    }
};

// High-level policy directly instantiates concrete database and payment classes!
class OrderService {
    PostgresOrderRepo repo_;      // Direct tight coupling
    RazorpayGateway gateway_;     // Direct tight coupling
public:
    OrderService() : repo_(), gateway_() {}

    bool checkout(const Order& order) {
        std::println("--- Processing Checkout for Order {} ---", order.id);
        if (!gateway_.charge(order.customerId, order.amountRupees)) {
            std::println("[OrderService] Payment failed for order {}", order.id);
            return false;
        }
        repo_.save(order);
        std::println("[OrderService] Order {} placed successfully!", order.id);
        return true;
    }
};

int main() {
    OrderService service;
    Order o1{"ORD-501", "cust_101", 450};
    service.checkout(o1);
    return 0;
}
Unit tests require live databases and network connections

Because OrderService hardcodes PostgresOrderRepo and RazorpayGateway, tests cannot run in isolation. A flaky network or downed staging database breaks the entire CI pipeline.

High-level domain rules couple directly to low-level drivers

The core business policy ('validate, charge, then persist order') directly references SQL connection strings and vendor HTTP SDKs β€” violating the Dependency Inversion Principle (DIP).

Rigid architecture prevents multi-region deployments

Swapping PostgreSQL for DynamoDB or Razorpay for Stripe requires editing and re-compiling OrderService, risking regressions in battle-tested checkout rules.

03

Step 2 β€” The fix: Dependency Inversion Principle (DIP) + Constructor Injection

Core idea in one sentence

High-level modules must not depend on low-level modules; both must depend on abstractions.

Instead of OrderService creating concrete dependencies, we define two pure virtual interfaces: OrderRepository and PaymentGateway. OrderService depends only on these abstract contracts, injected through its constructor. In production, we inject PostgresOrderRepo and RazorpayGateway; in unit tests, we inject InMemoryOrderRepo and FakePaymentGateway.

Pattern roles in our story
High-Level Policy (Consumer)

OrderService β€” orchestrates the checkout process; depends only on OrderRepository and PaymentGateway interfaces.

Abstraction (Repository)

OrderRepository β€” pure virtual interface declaring save(order) and findById(id) with a virtual destructor.

Abstraction (Payment)

PaymentGateway β€” pure virtual interface declaring charge(customerId, amountRupees).

Low-Level Details (Production)

PostgresOrderRepo and RazorpayGateway β€” implement the interfaces with real SQL queries and vendor network calls.

Low-Level Details (Testing)

InMemoryOrderRepo and FakePaymentGateway β€” zero-dependency test doubles that store data in memory and simulate network responses.

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