# Repository Pattern

## Problem

Business logic becomes tightly coupled to data access code (SQL queries, ORM calls, API requests). This makes the domain layer hard to test, hard to change persistence technology, and scatters query logic throughout the application.

## Solution

Introduce a **Repository** — an object that mediates between the domain layer and data mapping layers using a collection-like interface. Clients add, remove, and query domain objects as if working with an in-memory collection, while the repository handles all persistence concerns behind the scenes.

## When to Use

- You want to decouple domain logic from persistence details.
- You need to swap or test data stores independently (e.g., in-memory for tests, PostgreSQL for production).
- Your domain model is rich and queries are non-trivial.
- Multiple parts of the application access the same aggregate roots.

## When to Avoid

- Simple CRUD apps where an ORM already provides sufficient abstraction.
- When you have very few entities and no complex query logic.
- Microservices with single-entity data access where the overhead is not justified.

## Pseudocode

```
INTERFACE Repository<T>:
    METHOD add(entity: T)
    METHOD remove(entity: T)
    METHOD get_by_id(id) -> T
    METHOD list_all() -> List<T>

CLASS InMemoryRepository IMPLEMENTS Repository<T>:
    store = {}

    METHOD add(entity):
        store[entity.id] = entity

    METHOD remove(entity):
        DELETE store[entity.id]

    METHOD get_by_id(id):
        RETURN store[id]

    METHOD list_all():
        RETURN VALUES(store)
```

## Python

```python
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Dict, List, Optional


@dataclass
class User:
    id: int
    name: str
    email: str


class UserRepository(ABC):
    @abstractmethod
    def add(self, user: User) -> None: ...

    @abstractmethod
    def get_by_id(self, user_id: int) -> Optional[User]: ...

    @abstractmethod
    def list_all(self) -> List[User]: ...

    @abstractmethod
    def remove(self, user_id: int) -> None: ...


class InMemoryUserRepository(UserRepository):
    def __init__(self) -> None:
        self._store: Dict[int, User] = {}

    def add(self, user: User) -> None:
        self._store[user.id] = user
        print(f"  Added: {user}")

    def get_by_id(self, user_id: int) -> Optional[User]:
        return self._store.get(user_id)

    def list_all(self) -> List[User]:
        return list(self._store.values())

    def remove(self, user_id: int) -> None:
        removed = self._store.pop(user_id, None)
        if removed:
            print(f"  Removed: {removed}")


def main() -> None:
    repo: UserRepository = InMemoryUserRepository()

    print("--- Adding users ---")
    repo.add(User(1, "Alice", "alice@example.com"))
    repo.add(User(2, "Bob", "bob@example.com"))
    repo.add(User(3, "Charlie", "charlie@example.com"))

    print("\n--- Get by ID ---")
    user = repo.get_by_id(2)
    print(f"  Found: {user}")

    print("\n--- List all ---")
    for u in repo.list_all():
        print(f"  {u}")

    print("\n--- Remove user 1 ---")
    repo.remove(1)

    print("\n--- List all after removal ---")
    for u in repo.list_all():
        print(f"  {u}")


if __name__ == "__main__":
    main()
```

**Output:**
```
--- Adding users ---
  Added: User(id=1, name='Alice', email='alice@example.com')
  Added: User(id=2, name='Bob', email='bob@example.com')
  Added: User(id=3, name='Charlie', email='charlie@example.com')

--- Get by ID ---
  Found: User(id=2, name='Bob', email='bob@example.com')

--- List all ---
  User(id=1, name='Alice', email='alice@example.com')
  User(id=2, name='Bob', email='bob@example.com')
  User(id=3, name='Charlie', email='charlie@example.com')

--- Remove user 1 ---
  Removed: User(id=1, name='Alice', email='alice@example.com')

--- List all after removal ---
  User(id=2, name='Bob', email='bob@example.com')
  User(id=3, name='Charlie', email='charlie@example.com')
```

## Go

```go
package main

import "fmt"

// Domain entity
type User struct {
	ID    int
	Name  string
	Email string
}

// Repository interface
type UserRepository interface {
	Add(user User)
	GetByID(id int) (User, bool)
	ListAll() []User
	Remove(id int)
}

// In-memory implementation
type InMemoryUserRepository struct {
	store map[int]User
}

func NewInMemoryUserRepository() *InMemoryUserRepository {
	return &InMemoryUserRepository{store: make(map[int]User)}
}

func (r *InMemoryUserRepository) Add(user User) {
	r.store[user.ID] = user
	fmt.Printf("  Added: %+v\n", user)
}

func (r *InMemoryUserRepository) GetByID(id int) (User, bool) {
	u, ok := r.store[id]
	return u, ok
}

func (r *InMemoryUserRepository) ListAll() []User {
	users := make([]User, 0, len(r.store))
	for _, u := range r.store {
		users = append(users, u)
	}
	return users
}

func (r *InMemoryUserRepository) Remove(id int) {
	if u, ok := r.store[id]; ok {
		delete(r.store, id)
		fmt.Printf("  Removed: %+v\n", u)
	}
}

func main() {
	var repo UserRepository = NewInMemoryUserRepository()

	fmt.Println("--- Adding users ---")
	repo.Add(User{1, "Alice", "alice@example.com"})
	repo.Add(User{2, "Bob", "bob@example.com"})
	repo.Add(User{3, "Charlie", "charlie@example.com"})

	fmt.Println("\n--- Get by ID ---")
	if user, ok := repo.GetByID(2); ok {
		fmt.Printf("  Found: %+v\n", user)
	}

	fmt.Println("\n--- List all ---")
	for _, u := range repo.ListAll() {
		fmt.Printf("  %+v\n", u)
	}

	fmt.Println("\n--- Remove user 1 ---")
	repo.Remove(1)

	fmt.Println("\n--- List all after removal ---")
	for _, u := range repo.ListAll() {
		fmt.Printf("  %+v\n", u)
	}
}
```

**Output:**
```
--- Adding users ---
  Added: {ID:1 Name:Alice Email:alice@example.com}
  Added: {ID:2 Name:Bob Email:bob@example.com}
  Added: {ID:3 Name:Charlie Email:charlie@example.com}

--- Get by ID ---
  Found: {ID:2 Name:Bob Email:bob@example.com}

--- List all ---
  {ID:1 Name:Alice Email:alice@example.com}
  {ID:2 Name:Bob Email:bob@example.com}
  {ID:3 Name:Charlie Email:charlie@example.com}

--- Remove user 1 ---
  Removed: {ID:1 Name:Alice Email:alice@example.com}

--- List all after removal ---
  {ID:2 Name:Bob Email:bob@example.com}
  {ID:3 Name:Charlie Email:charlie@example.com}
```

## Related Patterns

- **Unit of Work** — Repository provides the collection-like interface; Unit of Work tracks changes and commits them atomically. Always use together.
- **Identity Map** — Repository should check the Identity Map before loading from DB, and register loaded objects in it, to avoid duplicate instances.
- **Data Mapper** — Repository uses Data Mapper internally to translate between domain objects and DB rows.
- **Specification** — Repository.find(specification) accepts a Specification to express query criteria without coupling to the query language.
- **Aggregate** — One Repository per Aggregate root. Never a Repository for non-root entities.
