Repository Design Pattern

Last Updated : 13 May, 2026

The Repository Design Pattern provides an abstraction layer between business logic and data storage, offering a consistent way to access and manage data while hiding the details of the underlying data source.

  • Separates business logic from data access logic by providing a centralized and standardized approach to data operations, improving maintainability.
  • Improves testability and flexibility by allowing data sources to be easily mocked, replaced, or adapted to different storage technologies.

Real-World Example

The Repository Design Pattern is like a librarian in a library.

Imagine you're at a library to find a book. You don't go into the storage room to search for it yourself, instead, you ask the librarian to help you find the book. The librarian knows where the books are kept and can give you the book you want without you having to worry about where it's stored.

  • In the same way, the Repository Design Pattern works as a librarian between a program and data (like books in a library).
  • Instead of the program directly looking for data, it asks for repository to find save, update, or delete the data it needs.

Implementation

Problem statement

Suppose you are developing an e-commerce application that needs to manage products. The application should be able to add new products, retrieve existing products, update product information, and delete products. Instead of directly interacting with the database, we will utilize the Repository Pattern to handle these operations.

Step 1: Define the Product Entity

The Product class defines the attributes of product, such as id, name and price. This serves as the basic data structure representing a product.

C++
// Product entity representing a product
class Product {
public:
    int id;
    std::string name;
    float price;

    Product(int id, std::string name, float price) : id(id), name(std::move(name)), price(price) {}
};
Java
/* Product entity representing a product */
public class Product {
    public int id;
    public String name;
    public float price;

    public Product(int id, String name, float price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}
Python
class Product:  # Product entity representing a product
    def __init__(self, id, name, price):
        self.id = id
        self.name = name
        self.price = price
JavaScript
// Product entity representing a product
class Product {
    constructor(id, name, price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}
  • Defines the Product class as a blueprint for creating product objects.
  • Each product has a unique id, a name, and a price.

Step 2: Create the Repository Interface

The ProductRepository class is an abstract class that declares methods to manage products, such as adding, retrieving, updating, and deleting.

C++
// Interface for the repository
class ProductRepository {
public:
    virtual void addProduct(const Product& product) = 0;
    virtual Product getProductById(int productId) = 0;
    virtual void updateProduct(const Product& product) = 0;
    virtual void deleteProduct(int productId) = 0;
};
Java
import java.util.List;

interface ProductRepository {
    void addProduct(Product product);
    Product getProductById(int productId);
    void updateProduct(Product product);
    void deleteProduct(int productId);
}
Python
from abc import ABC, abstractmethod

class ProductRepository(ABC):
    @abstractmethod
    def add_product(self, product):
        pass

    @abstractmethod
    def get_product_by_id(self, product_id):
        pass

    @abstractmethod
    def update_product(self, product):
        pass

    @abstractmethod
    def delete_product(self, product_id):
        pass
JavaScript
class ProductRepository {
    addProduct(product) {
        throw new Error('Method not implemented.');
    }
    getProductById(productId) {
        throw new Error('Method not implemented.');
    }
    updateProduct(product) {
        throw new Error('Method not implemented.');
    }
    deleteProduct(productId) {
        throw new Error('Method not implemented.');
    }
}
  • Defines a set of rules (interface) that any product repository class must follow.
  • Specifies the exact methods that a repository class must implement.

Step 3: Implement a Concrete Repository

The InMemoryProductRepository class is a concrete implementation of the ProductRepository. It uses an in-memory data structure (here, a vector) to manage products.

C++
// Concrete implementation of the repository (in-memory repository)
class InMemoryProductRepository : public ProductRepository {
private:
    std::vector<Product> products;

public:
    void addProduct(const Product& product) override {
        products.push_back(product);
    }

    Product getProductById(int productId) override {
        for (const auto& product : products) {
            if (product.id == productId) {
                return product;
            }
        }
        return Product(-1, "Not Found", 0.0); // Return a default product if not found
    }

    void updateProduct(const Product& updatedProduct) override {
        for (auto& product : products) {
            if (product.id == updatedProduct.id) {
                product = updatedProduct;
                return;
            }
        }
    }

    void deleteProduct(int productId) override {
        products.erase(std::remove_if(products.begin(), products.end(),
            [productId](const Product& product) { return product.id == productId; }),
            products.end());
    }
};
Java
import java.util.ArrayList;
import java.util.List;

class Product {
    private int id;
    private String name;
    private double price;

    public Product(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}

interface ProductRepository {
    void addProduct(Product product);
    Product getProductById(int productId);
    void updateProduct(Product updatedProduct);
    void deleteProduct(int productId);
}

class InMemoryProductRepository implements ProductRepository {
    private List<Product> products = new ArrayList<>();

    @Override
    public void addProduct(Product product) {
        products.add(product);
    }

    @Override
    public Product getProductById(int productId) {
        for (Product product : products) {
            if (product.getId() == productId) {
                return product;
            }
        }
        return new Product(-1, "Not Found", 0.0); // Return a default product if not found
    }

    @Override
    public void updateProduct(Product updatedProduct) {
        for (int i = 0; i < products.size(); i++) {
            if (products.get(i).getId() == updatedProduct.getId()) {
                products.set(i, updatedProduct);
                return;
            }
        }
    }

    @Override
    public void deleteProduct(int productId) {
        products.removeIf(product -> product.getId() == productId);
    }
}
Python
class Product:
    def __init__(self, id, name, price):
        self.id = id
        self.name = name
        self.price = price

class ProductRepository:
    def addProduct(self, product):
        pass

    def getProductById(self, productId):
        pass

    def updateProduct(self, updatedProduct):
        pass

    def deleteProduct(self, productId):
        pass

class InMemoryProductRepository(ProductRepository):
    def __init__(self):
        self.products = []

    def addProduct(self, product):
        self.products.append(product)

    def getProductById(self, productId):
        for product in self.products:
            if product.id == productId:
                return product
        return Product(-1, "Not Found", 0.0)  # Return a default product if not found

    def updateProduct(self, updatedProduct):
        for i, product in enumerate(self.products):
            if product.id == updatedProduct.id:
                self.products[i] = updatedProduct
                return

    def deleteProduct(self, productId):
        self.products = [product for product in self.products if product.id!= productId]
JavaScript
class Product {
    constructor(id, name, price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

class ProductRepository {
    addProduct(product) {}
    getProductById(productId) {}
    updateProduct(updatedProduct) {}
    deleteProduct(productId) {}
}

class InMemoryProductRepository extends ProductRepository {
    constructor() {
        super();
        this.products = [];
    }

    addProduct(product) {
        this.products.push(product);
    }

    getProductById(productId) {
        for (const product of this.products) {
            if (product.id === productId) {
                return product;
            }
        }
        return new Product(-1, 'Not Found', 0.0); // Return a default product if not found
    }

    updateProduct(updatedProduct) {
        for (let i = 0; i < this.products.length; i++) {
            if (this.products[i].id === updatedProduct.id) {
                this.products[i] = updatedProduct;
                return;
            }
        }
    }

    deleteProduct(productId) {
        this.products = this.products.filter(product => product.id!== productId);
    }
}
  • Implements the methods declared in the ProductRepository interface.
  • Uses an in-memory data store (vector) to add, retrieve, update, and delete products.

Step 4: Usage in Main Function

The main function demonstrates the usage of the InMemoryProductRepository by performing various operations on products.

C++
int main() {
    InMemoryProductRepository productRepo;

    // Adding products
    productRepo.addProduct(Product(1, "Keyboard", 25.0));
    productRepo.addProduct(Product(2, "Mouse", 15.0));

    // Retrieving and updating product
    Product retrievedProduct = productRepo.getProductById(1);
    std::cout << "Retrieved Product: " << retrievedProduct.name << " - $" << retrievedProduct.price << std::endl;

    retrievedProduct.price = 30.0;
    productRepo.updateProduct(retrievedProduct);

    // Deleting a product
    productRepo.deleteProduct(2);

    return 0;
}
Java
import java.util.ArrayList;
import java.util.List;

class Product {
    int id;
    String name;
    double price;

    public Product(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

class InMemoryProductRepository {
    private List<Product> products = new ArrayList<>();

    public void addProduct(Product product) {
        products.add(product);
    }

    public Product getProductById(int id) {
        for (Product product : products) {
            if (product.id == id) {
                return product;
            }
        }
        return null;
    }

    public void updateProduct(Product product) {
        for (int i = 0; i < products.size(); i++) {
            if (products.get(i).id == product.id) {
                products.set(i, product);
                break;
            }
        }
    }

    public void deleteProduct(int id) {
        products.removeIf(product -> product.id == id);
    }
}

public class Main {
    public static void main(String[] args) {
        InMemoryProductRepository productRepo = new InMemoryProductRepository();

        // Adding products
        productRepo.addProduct(new Product(1, "Keyboard", 25.0));
        productRepo.addProduct(new Product(2, "Mouse", 15.0));

        // Retrieving and updating product
        Product retrievedProduct = productRepo.getProductById(1);
        System.out.println("Retrieved Product: " + retrievedProduct.name + " - $" + retrievedProduct.price);

        retrievedProduct.price = 30.0;
        productRepo.updateProduct(retrievedProduct);

        // Deleting a product
        productRepo.deleteProduct(2);
    }
}
Python
class Product:
    def __init__(self, id, name, price):
        self.id = id
        self.name = name
        self.price = price

class InMemoryProductRepository:
    def __init__(self):
        self.products = []

    def add_product(self, product):
        self.products.append(product)

    def get_product_by_id(self, id):
        for product in self.products:
            if product.id == id:
                return product
        return None

    def update_product(self, product):
        for i, p in enumerate(self.products):
            if p.id == product.id:
                self.products[i] = product
                break

    def delete_product(self, id):
        self.products = [product for product in self.products if product.id!= id]

if __name__ == '__main__':
    product_repo = InMemoryProductRepository()

    # Adding products
    product_repo.add_product(Product(1, 'Keyboard', 25.0))
    product_repo.add_product(Product(2, 'Mouse', 15.0))

    # Retrieving and updating product
    retrieved_product = product_repo.get_product_by_id(1)
    print(f'Retrieved Product: {retrieved_product.name} - ${retrieved_product.price}')

    retrieved_product.price = 30.0
    product_repo.update_product(retrieved_product)

    # Deleting a product
    product_repo.delete_product(2)
JavaScript
class Product {
    constructor(id, name, price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

class InMemoryProductRepository {
    constructor() {
        this.products = [];
    }

    addProduct(product) {
        this.products.push(product);
    }

    getProductById(id) {
        for (let product of this.products) {
            if (product.id === id) {
                return product;
            }
        }
        return null;
    }

    updateProduct(product) {
        for (let i = 0; i < this.products.length; i++) {
            if (this.products[i].id === product.id) {
                this.products[i] = product;
                break;
            }
        }
    }

    deleteProduct(id) {
        this.products = this.products.filter(product => product.id!== id);
    }
}

const productRepo = new InMemoryProductRepository();

// Adding products
productRepo.addProduct(new Product(1, 'Keyboard', 25.0));
productRepo.addProduct(new Product(2, 'Mouse', 15.0));

// Retrieving and updating product
let retrievedProduct = productRepo.getProductById(1);
console.log(`Retrieved Product: ${retrievedProduct.name} - $${retrievedProduct.price}`);

retrievedProduct.price = 30.0;
productRepo.updateProduct(retrievedProduct);

// Deleting a product
productRepo.deleteProduct(2);
  • The main function acts as the entry point and demonstrates the use of the InMemoryProductRepository.
  • It adds products, retrieves a product, updates its price, and deletes a product to showcase repository operations.

This code implements a simple in-memory repository for demonstration purposes, but in real-world scenario, the repository would likely interact with a database or some other persistent storage.

Advantages

The Repository Design Pattern offers several benefits that improve the structure and quality of an application.

  • Improves separation of concerns by isolating data access from business logic.
  • Enhances testability by allowing easy mocking of data repositories.
  • Increases maintainability by centralizing data access logic.
  • Provides flexibility to switch or modify data sources without impacting business code.

Disadvantages

The disadvantages of Repository Design Pattern are

  • In small applications, implementing this pattern can add unnecessary complexity, making it more cumbersome than helpful.
  • Adopting the repository pattern requires time to set up interfaces and repository classes, which can delay project timelines.
  • Sometimes, the details of the underlying data access can leak into higher layers, reducing the effectiveness of the abstraction.

Use Cases

The use cases of Repository Design Pattern are

  • Web Applications: It’s widely used in web apps to manage database interactions, making it easier to switch between different database systems.
  • APIs and Services: In APIs or microservices, this pattern organizes data access and ensures consistent interactions with data.
  • Large Systems: For complex systems, the repository pattern helps keep data access logic tidy, making the codebase easier to maintain.
  • Testing Environments: It’s useful for creating mock repositories in testing, allowing you to simulate data access without affecting real data.
  • Data Migration: When moving data between databases, the repository pattern makes transitions smoother by allowing you to swap implementations while keeping the application intact.

Limitation: Over-Abstraction of Data Sources

The Repository Pattern works well when dealing with similar types of data sources (like relational databases). However, not all data sources behave the same way.

For example:

  • A database (e.g., Prisma) supports transactions and complex queries.
  • An object storage system (e.g., S3) supports file uploads and pre-signed URLs.

If we try to force both into a single generic repository interface (CRUD), we may lose these specialized capabilities.

Comment

Explore