Skip to main content
Design Patterns – Complete Beginner to Advanced Guide
CHAPTER 17 Intermediate

Repository and Dependency Injection Patterns

Updated: May 16, 2026
40 min read

# CHAPTER 17

Repository and Dependency Injection Patterns

1. Introduction

While the Gang of Four formalized the classic 23 design patterns in 1994, modern enterprise web development has evolved. Today, if you open any enterprise framework (Laravel, Spring Boot, .NET Core), you will immediately encounter two foundational architectural patterns that dictate how the entire application is structured: the Repository Pattern and Dependency Injection (DI). If a developer hardcodes SQL queries into a controller, or manually instantiates 10 nested database dependencies using the new keyword, the application becomes an untestable, tightly coupled monolith. In this chapter, we will master modern backend architecture. We will abstract the database using Repositories, and achieve ultimate decoupling using Dependency Injection and Service Containers.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Define the intent and structural benefits of the Repository Pattern.
  • Abstract the Data Access Layer (DAL) away from the Business Logic.
  • Understand the core concept of Dependency Injection (DI) and Inversion of Control (IoC).
  • Architect highly decoupled systems using Constructor Injection.
  • Understand how DI facilitates isolated Unit Testing (Mocking).

3. The Repository Pattern

The Repository Pattern mediates between the domain (business logic) and data mapping layers using a collection-like interface for accessing domain objects.
  • The Problem: Your UserController directly calls $db->query("SELECT * FROM users"). Tomorrow, the CTO decides to switch from MySQL to MongoDB. You have to rewrite every single controller in your application.
  • The Solution: You create a UserRepository interface with a find($id) method. The Controller only talks to the interface. You write a MySQLUserRepository class that implements the interface. The Controller has no idea *how* the data is fetched; it just knows it gets a User object.

4. Dependency Injection (DI)

Dependency Injection is a technique in which an object receives other objects that it depends on (called dependencies).
  • The Problem: A ReportService needs a Database and a Mailer. If the ReportService constructor calls new Database() and new Mailer(), it is permanently glued to those concrete classes (violating the SOLID Dependency Inversion Principle). You cannot test the ReportService without actually sending a real email and hitting a real database.
  • The Solution (Constructor Injection): You pass the dependencies *into* the ReportService through its constructor.
$service = new ReportService(new Database(), new Mailer());

5. Inversion of Control (IoC) Containers

If class A needs B, and B needs C and D, manually writing new A(new B(new C(), new D())) everywhere is exhausting.
  • The Service Container: Modern frameworks use an IoC Container. It is a massive registry. You teach the container: "Whenever a class asks for a MailerInterface, give it an instance of SendGridMailer." The framework magically injects the correct objects automatically.

6. UML Diagram

*Repository and DI Architecture*
text
12345678
[ Controller (Business Logic) ] 
       |
       | (Depends on Abstraction via Constructor Injection)
       v
[ <<Interface>> UserRepository ]
       ^
       | (Implements)
[ MySQLUserRepository ] ---> [ Actual SQL Database ]

7. Code Example (PHP)

Let's build a highly decoupled, perfectly testable architecture.
php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
<?php
// --- 1. The Repository Interface (The Abstraction) ---
interface UserRepository {
    public function findById($id);
}

// --- 2. The Concrete Repository (The Implementation) ---
class MySQLUserRepository implements UserRepository {
    private $dbConnection; // Imagine this is a PDO object
    
    public function __construct($db) { $this->dbConnection = $db; }

    public function findById($id) {
        // Raw database logic is isolated here!
        echo "Executing SQL: SELECT * FROM users WHERE id = $id\n";
        return ["id" => $id, "name" => "John Doe"]; // Fake data
    }
}

// --- 3. A Secondary Dependency ---
interface Mailer {
    public function sendWelcome($email);
}

class SMTPMailer implements Mailer {
    public function sendWelcome($email) { echo "Sending SMTP email to $email\n"; }
}

// --- 4. The Business Logic (The Service) ---
class UserRegistrationService {
    private $userRepo;
    private $mailer;

    // DEPENDENCY INJECTION via Constructor
    // We only depend on INTERFACES, not concrete classes.
    public function __construct(UserRepository $repo, Mailer $mailer) {
        $this->userRepo = $repo;
        $this->mailer = $mailer;
    }

    public function registerUser($id) {
        // The service doesn't know about SQL or SMTP. It just uses the interfaces.
        $user = $this->userRepo->findById($id);
        echo "User registered: " . $user[&#039;name'] . "\n";
        $this->mailer->sendWelcome("john@example.com");
    }
}

// --- 5. Application Bootstrap (IoC Wiring) ---
// In a modern framework, the "Container" does this automatically.
$db = "Fake Database Connection";
$mySqlRepo = new MySQLUserRepository($db);
$smtpMailer = new SMTPMailer();

// Inject the dependencies!
$registrationService = new UserRegistrationService($mySqlRepo, $smtpMailer);
$registrationService->registerUser(5);

// Output:
// Executing SQL: SELECT * FROM users WHERE id = 5
// User registered: John Doe
// Sending SMTP email to john@example.com
?>

8. The Testing Benefit (Mocking)

Because UserRegistrationService depends on interfaces, Unit Testing becomes incredibly easy. You don't want your test to actually hit a real MySQL database. Instead, you create a MockUserRepository class that implements the interface but just returns dummy data instantly in memory. You inject the Mock into the Service, allowing you to test the Service's logic in 1 millisecond with zero database required.

9. Common Mistakes

  • The "Active Record" Confusion: Frameworks like Laravel use the "Active Record" pattern by default (User::find(1)). This inherently couples your logic directly to the database ORM, bypassing the Repository pattern. While Active Record is incredibly fast for rapid prototyping, Enterprise architectures strictly prefer the decoupled Repository pattern to isolate the domain logic from the ORM.

10. Mini Project: Swap the Database

  1. 1. Using the code above, the CTO dictates that user data must now be fetched from an external REST API instead of MySQL.
  1. 2. Create a RestApiUserRepository class that implements UserRepository.
  1. 3. In the bootstrap section, change exactly *one line of code* to inject new RestApiUserRepository() instead of the MySQL version.
  1. 4. Run the code. Notice how the UserRegistrationService required absolutely zero modifications. That is the power of decoupled architecture.

11. Practice Exercises

  1. 1. Define the architectural purpose of the Repository Pattern. How does it protect your core business logic from changes in third-party database drivers?
  1. 2. Explain the concept of Dependency Injection. Why is injecting dependencies via a constructor vastly superior to instantiating them directly with the new keyword?

12. MCQs with Answers

Question 1

A developer writes a PaymentService class. Inside its constructor, it explicitly calls $this->db = new MySQLConnection(). Which SOLID principle has the developer violently violated, making the class permanently coupled to MySQL and impossible to isolate for unit testing?

Question 2

Modern enterprise frameworks utilize a centralized registry to automatically manage object instantiation and automatically inject required interfaces into class constructors. What is this architectural component called?

13. Interview Questions

  • Q: Explain how Dependency Injection facilitates rapid, isolated Unit Testing. Walk me through the concept of "Mocking" an interface.
  • Q: Compare the "Active Record" pattern with the "Repository" pattern for database access. If you are building a massive, 10-year enterprise SaaS application, which pattern would you choose and why?
  • Q: Walk me through the mechanics of an IoC (Inversion of Control) Container. How does a framework like Laravel or Spring "magically" know which concrete class to provide when a controller asks for an interface?

14. FAQs

Q: Is Dependency Injection the same as the Dependency Inversion Principle (DIP)? A: No, but they are closely related. DIP is the *concept* (depend on abstractions, not concretions). Dependency Injection is the *technique* used to achieve DIP (passing those abstractions into the constructor).

15. Summary

In Chapter 17, we engineered the foundation of modern backend architecture. We deployed the Repository pattern to build an impenetrable wall between our pristine business logic and the messy realities of SQL queries and database drivers. We mastered Dependency Injection, permanently eradicating the hardcoded new keyword from our domain services. By injecting interfaces rather than concrete classes, we achieved ultimate architectural decoupling, allowing us to swap databases instantly and execute isolated, lightning-fast unit tests. This is how enterprise software is built.

16. Next Chapter Recommendation

Our backend is decoupled and testable. Now we must connect it to the user interface and structure the overall application flow. Proceed to Chapter 18: MVC Architecture and Enterprise Patterns.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·