The Repository Pattern

Play this article

Building web software will always include some kind of database access. For this guide, it does not matter, whether the data access is done via SQL or NoSQL, Google Sheets, whatsoever. Since this post is all about the repository pattern. An often encountered set of classes and interfaces. Let me explain!

What is it used for?

  1. The repository pattern is used to encapsulate data access, so you don't have to spin up a database connection in your controllers or views.

  2. It's often built with the Adapter Pattern in mind. To the extent, that you can swap out the implementation for the database calls. Allowing you to either use a NoSQL, SQL or SQLite database.

How often will I find it?

From my experience, every backend application that talks to some sort of database is using the repository concept in some way. It may not be named that way (which it actually should be), but the concept remains the same.

What are the benefits?

  • Separation of concerns. Your code will become easier to maintain.

  • Your code will become cleaner. You don't need to write raw SQL within a controller.

  • Your code will become more secure. Since you don't write raw SQL at any point, but the repository, you only need to prevent eg. SQL injection at the repository layer

How is it accomplished?

The most basic implementation just consists of one class, with five CRUD operations.

class UserRepository
  def create(model_payload); end
  def read(id); end
  def list(); end
  def update(id, model_payload); end
  def delete(id); end
end

More generic approaches introduce a superclass, to make building repositories for specific models easier and DRY (Don't repeat yourself).

Please note: We will have a repository for each of our models.

I have built a factory method for a javascript application, that looks like this.

export const StubRepositoryFor = (formDefinition, sampleData = []) => {
    const items = sampleData; 
    return {
        create: (data) => {
            const item = {id: sampleData.length + 1, ...data};
            items.push(item);
            console.log("Created ", item);
            return item;
        },
        delete: (id) => {
            const index = items.findIndex(c => c.id == id);
            items.splice(index, 1);
            return true;
        },
        update: (id, updatePayload) => {
            console.log("updating " + id + " with payload", updatePayload);
            const itemIndex = items.findIndex(c => c.id == id);
            items[itemIndex] = {
                ...items[itemIndex],
                ...updatePayload,
            };
            return {id, ...updatePayload};
        },
        list: () => {
            return items;
        },
        get: (id) => {
            return items.find(c => c.id === id);
        },
        count: () => {
            return items?.length;
        },
    }
}

With this factory, one can easily create a new repository for a model, like this:

export const UserRepository = StubRepositoryFor([{username: 'IJustDev', first_name: 'Alexander', last_name: 'Panov'}]);

You may have already noticed, that there will be one problem...

Limitations of the Repository factory/superclass

It only works for CRUD resources, which are all managed in the same way.

If you were to introduce this pattern to your front end (which is what I did), you need to make sure, that the API requests are all the same for each model. In other words, there has to be a:

  • /create route for student, user, insert-random-model-name-here

  • /list route for student, user, insert-random-model-name-here

  • /get/:id route for student, user, insert-random-model-name-here

  • /update/:id route for student, user, insert-random-model-name-here

  • /delete/:id route for student, user, insert-random-model-name-here

for every resource (student,user, post, what-ever).

Using a serverless backend, eg. Firebase, Supabase or AppWrite, will allow you to easily do it.

Having a different backend, which does not conform to the REST standard will make this approach unusable.

Conclusion

A repository is more than just a concept on GitHub. It takes care of keeping your code clean from random database access scattered around hundreds and thousands of files.

Did you learn something new from this post? Never miss valuable knowledge about clean software development by subscribing to my free newsletter.