Introduction
Enterprise applications power modern businesses. They manage vast amounts of data, automate workflows, and support critical systems like banking, logistics, and HR. These apps face unique challenges: handling complexity, ensuring scalability, and maintaining clean architecture.
Martin Fowler's Patterns of Enterprise Application Architecture (2002) is a cornerstone. It catalogs patterns from real-world systems, covering layers, domain logic, and data mapping. Among them, the Repository Pattern stands out for data access.
Why It Matters?
Data is king in enterprise apps. The Repository Pattern decouples business logic from data storage. It acts as a mediator, providing a uniform interface for CRUD operations. This promotes testability, flexibility, and maintainability. Without it, code becomes tangled, hard to change.
Today, we dive into the Repository Pattern with a Python example. Let's build an employee management system.
What is the Repository Pattern?
It encapsulates data access. Abstracts CRUD operations (Create, Read, Update, Delete) into a collection-like interface. Domain objects don't touch the DB directly; they use the repository.
Key Benefits:
- Clear Separation: Pure business logic, no infrastructure ties.
- Easy Testing: Mock repositories for unit tests.
- Flexibility: Switch DB (SQL to NoSQL) without breaking code.
- Reusable: Common interface for entities.
Practical Example in Python
Employee management system. We use an in-memory list (easy to adapt to real DB).
Employee Entity:
This class represents an employee in our domain. It holds basic attributes like ID, name, position, and salary. The __repr__ method provides a readable string representation for debugging and logging.
class Employee:
def __init__(self, id: int, name: str, position: str, salary: float):
self.id = id
self.name = name
self.position = position
self.salary = salary
def __repr__(self):
return f"Employee(id={self.id}, name='{self.name}', position='{self.position}', salary={self.salary})"
Repository Interface:
Defines the contract for data access. Uses abstract methods to ensure any implementation (in-memory, SQL, etc.) follows the same interface. This abstraction allows swapping storage without changing business logic.
from abc import ABC, abstractmethod
from typing import List, Optional
class EmployeeRepository(ABC):
@abstractmethod
def add(self, employee: Employee) -> None: pass
@abstractmethod
def get_by_id(self, id: int) -> Optional[Employee]: pass
@abstractmethod
def get_all(self) -> List[Employee]: pass
@abstractmethod
def update(self, employee: Employee) -> None: pass
@abstractmethod
def delete(self, id: int) -> None: pass
In-Memory Implementation:
A concrete repository using a list. Manages employee data in memory. add assigns a unique ID. get_by_id finds by ID. get_all returns a copy to avoid mutations. update replaces the employee. delete removes by ID. Simple, but demonstrates the pattern.
class InMemoryEmployeeRepository(EmployeeRepository):
def __init__(self):
self._employees = []
self._next_id = 1
def add(self, employee: Employee) -> None:
employee.id = self._next_id
self._next_id += 1
self._employees.append(employee)
def get_by_id(self, id: int) -> Optional[Employee]:
return next((emp for emp in self._employees if emp.id == id), None)
def get_all(self) -> List[Employee]:
return self._employees.copy()
def update(self, employee: Employee) -> None:
for i, emp in enumerate(self._employees):
if emp.id == employee.id:
self._employees[i] = employee
break
def delete(self, id: int) -> None:
self._employees = [emp for emp in self._employees if emp.id != id]
Business Service:
Contains business logic. Depends on the repository interface, not implementation. Methods like hire_employee create and add employees. promote_employee updates position and salary. This layer focuses on rules, not data storage.
class EmployeeService:
def __init__(self, repository: EmployeeRepository):
self.repository = repository
def hire_employee(self, name: str, position: str, salary: float) -> Employee:
employee = Employee(0, name, position, salary)
self.repository.add(employee)
return employee
def get_employee(self, id: int) -> Optional[Employee]:
return self.repository.get_by_id(id)
def list_employees(self) -> List[Employee]:
return self.repository.get_all()
def promote_employee(self, id: int, new_position: str, new_salary: float) -> bool:
employee = self.repository.get_by_id(id)
if employee:
employee.position = new_position
employee.salary = new_salary
self.repository.update(employee)
return True
return False
# Demo
if __name__ == "__main__":
repo = InMemoryEmployeeRepository()
service = EmployeeService(repo)
emp1 = service.hire_employee("Alice Johnson", "Developer", 75000.0)
emp2 = service.hire_employee("Bob Smith", "Manager", 90000.0)
print("Employees:")
for emp in service.list_employees():
print(emp)
service.promote_employee(emp1.id, "Senior Developer", 85000.0)
print("\nAfter promotion:")
print(service.get_employee(emp1.id))
Result: Modular, testable, scalable code. Business logic is separate from data access, making it easy to test services with mocks and switch repositories.
Conclusion
The Repository Pattern transforms enterprise apps: cleaner, more flexible, maintainable. Try in Python or adapt to your stack.
Read More: Repository in Fowler
References
- Fowler, M. (2002). Patterns of Enterprise Application Architecture. Addison-Wesley.
- Official site: https://martinfowler.com/eaaCatalog/
Top comments (0)