Introduction
The repository pattern is a design pattern used to abstract the data access layer from the business logic of an application. By using repositories, you can interact with data without needing to know the details of how the data is stored or retrieved. This promotes separation of concerns, testability, and flexibility in choosing or changing data storage strategies without impacting the rest of the codebase.
Purpose
Provides a consistent interface to manage data access, abstracting away the details of how data is stored or retrieved. Promotes separation of concerns, testability, and flexibility in changing storage mechanisms.
Use Cases
- Abstracting database operations to allow swapping between SQL, NoSQL, or in-memory storage.
- Testing business logic without relying on real databases.
- Centralizing data access logic for complex applications.
- Managing collections of objects in memory with a consistent interface.
1from abc import ABC, abstractmethod2 3# Abstract Repository4class Repository(ABC):5 @abstractmethod6 def add(self, item):7 pass8 9 @abstractmethod10 def get(self, item_id):11 pass12 13# Concrete Repository14class InMemoryRepository(Repository):15 def __init__(self):16 self._data = {}17 18 def add(self, item):19 self._data[item["id"]] = item20 21 def get(self, item_id):22 return self._data.get(item_id)23 24# Usage25repo = InMemoryRepository()26repo.add({"id": 1, "name": "Item 1"})27print(repo.get(1)) # Output: {'id': 1, 'name': 'Item 1'}1// Abstract Repository is not enforced in JS, but we define a concrete repository2class InMemoryRepository {3 constructor() {4 this.data = new Map();5 }6 7 add(item) {8 this.data.set(item.id, item);9 }10 11 get(itemId) {12 return this.data.get(itemId);13 }14}15 16// Usage17const repo = new InMemoryRepository();18repo.add({ id: 1, name: "Item 1" });19console.log(repo.get(1)); // Output: { id: 1, name: "Item 1" }1// Abstract Repository2abstract class Repository<T> {3 void add(T item);4 T? get(int id);5}6 7// Concrete Repository8class InMemoryRepository<T> implements Repository<T> {9 final Map<int, T> _data = {};10 11 12 void add(T item) {13 if (item is Map && item.containsKey("id")) {14 _data[item["id"]] = item;15 }16 }17 18 19 T? get(int id) {20 return _data[id];21 }22}23 24// Usage25void main() {26 final repo = InMemoryRepository<Map<String, dynamic>>();27 repo.add({"id": 1, "name": "Item 1"});28 print(repo.get(1)); // Output: {id: 1, name: Item 1}29}1# Abstract Repository is not enforced in Ruby, but we define a concrete repository2class InMemoryRepository3 def initialize4 @data = {}5 end6 7 def add(item)8 @data[item[:id]] = item9 end10 11 def get(item_id)12 @data[item_id]13 end14end15 16# Usage17repo = InMemoryRepository.new18repo.add({ id: 1, name: "Item 1" })19puts repo.get(1) # Output: {:id=>1, :name=>"Item 1"}Conclusion
In this example, the Repository abstract class defines a consistent interface for data access, while InMemoryRepository provides a concrete implementation using an in-memory dictionary. The repository pattern allows you to swap out the storage mechanism later—such as using a database or an external API—without changing the parts of your application that rely on the repository. This makes your code more modular, maintainable, and easier to test.