Design Patterns Repository

The Repository pattern hides data storage behind a collection-like interface. Business code asks the repository to add, find, or remove items. The code never sees SQL statements, file paths, or network calls.

The Real-World Analogy

A librarian manages a library. Readers ask for a book by title, and the librarian fetches it from shelves, archives, or a partner library. None of them learn where the book lives or how the catalog works.

Problem Without a Repository

class StudentService {
    void enroll(String id) {
        run("SELECT * FROM students WHERE id = " + id);   // storage detail
        ...
        run("UPDATE students SET course = ...");          // storage detail
    }
}

Business rules and database commands live in the same class.
Switching databases means rewriting business code.

Solution With a Repository

┌──────────────────┐   asks    ┌──────────────────────┐
│ StudentService   │ ────────▶ │ «interface»          │
│ (business rules) │           │ StudentRepository    │
└──────────────────┘           │ + findById(id)       │
                               │ + add(student)       │
                               │ + remove(student)    │
                               └──────────▲───────────┘
                          ┌───────────────┼────────────────┐
              ┌───────────┴───┐   ┌───────┴───────┐  ┌─────┴────────────┐
              │ SqlRepository │   │ FileRepository│  │InMemoryRepository│
              └───────────────┘   └───────────────┘  └──────────────────┘

Services speak the language of the business: students, orders, and products. The repository translates that language into storage commands.

Layers in Action

UI ──▶ Service ──▶ Repository ──▶ Database
                   (interface)
                        ▲
                        │ replaced by InMemoryRepository in tests

Code Example

interface StudentRepository {
    Student findById(String id);
    List findAll();
    void add(Student student);
    void remove(String id);
}

class SqlStudentRepository implements StudentRepository {
    public Student findById(String id) {
        Row row = db.query("SELECT * FROM students WHERE id = ?", id);
        return new Student(row.name, row.course);
    }
    public void add(Student student) {
        db.execute("INSERT INTO students VALUES (?, ?)", student.name, student.course);
    }
    ...
}

class InMemoryStudentRepository implements StudentRepository {
    private Map items = new HashMap();
    public Student findById(String id) { return items.get(id); }
    public void add(Student s)         { items.put(s.id, s); }
    ...
}

class StudentService {
    private StudentRepository repo;
    StudentService(StudentRepository repo) { this.repo = repo; }

    void enroll(String id, String course) {
        Student s = repo.findById(id);
        s.setCourse(course);
        repo.add(s);
    }
}

Tests use the in-memory version and run in milliseconds. Production uses the SQL version.

What Belongs in a Repository

  • Methods that fetch or save whole business objects.
  • Query methods named after business questions, such as findActiveStudents.
  • Nothing about screens, formatting, or business decisions.

Repository Versus Similar Ideas

IdeaFocus
RepositoryCollection of business objects
Data Access ObjectTable-level operations
FacadeSimplified entry to many classes
CacheFast temporary copy of data

Common Mistakes

  • Exposing database query objects to callers. Storage details leak again.
  • Creating one giant repository for every table. Keep one per business concept.
  • Putting business rules inside repository methods.

Advantages and Disadvantages

AdvantagesDisadvantages
Storage becomes replaceableExtra layer for tiny projects
Business code stays cleanComplex queries strain the interface
Fast tests with in-memory versionsPoor design hides slow queries

Step-by-Step Walkthrough

Follow enroll() in the code example. StudentService asks the repository for a student by id. The SQL repository runs a query, reads the row, and builds a Student object. Updating the course on the object, the service sends it back with add(). Storage receives the change from the repository.

The service never mentions tables or columns. Tests swap in the in-memory version without touching a database.

Designing Repository Methods

  • Name methods after business questions, such as findOverdueInvoices.
  • Return business objects rather than raw rows.
  • Keep paging and sorting options in simple parameters.
  • Hide transaction details behind the interface when possible.

Practice Idea

Write a ProductRepository with an in-memory implementation. Add three products, find one by id, and remove another. Later, imagine replacing the storage with a file, and notice that the calling code stays the same.

Plain-Words Summary

Repositories bring a calm boundary to a codebase. Everything above the boundary speaks in business terms, and everything below it speaks in storage terms. Switching from a file to a database, or from one database vendor to another, becomes a task for one layer. The rest of the program continues without noticing, which saves weeks on long projects.

Key Takeaways

  • A repository offers collection-style access to stored objects.
  • Business code depends on the interface, never on the database.
  • In-memory repositories speed up testing.
  • Keep repositories small and focused on one business concept.