PHP
Article
By Amit Gupta

Repository Design Pattern Demystified

By Amit Gupta
Help us help you! You'll get a... FREE 6-Month Subscription to SitePoint Premium Plus you'll go in the draw to WIN a new Macbook SitePoint 2017 Survey Yes, let's Do this It only takes 5 min

What is the Repository Design Pattern?

To put it simply, it is an implementation of a brokering layer between the application and a data source. Neither party needs to be be aware of the other to perform their respective jobs which allows us to have a decoupled architecture which in turn helps in the scaling of the application in the big leagues without having hard dependencies.

Why should you care?

Let us understand this with an example. Imagine we are building an online store which sells orange flavored candies. It’s a small store and it keeps local inventory, so we don’t need anything fancy here. The storefront application can just hook into the database and take orders online based on how much inventory is at hand. This will work fine since the store has only one supply warehouse and has a limited area of operation. But what will happen if this store wants to expand its area of operation? The store might want to expand into another city or across the country and having a central inventory system would be so cumbersome.

Now if we are still using data models then we have a somewhat tightly coupled application. The storefront application needs to be aware of every data source it has to interact with and that is a poor application design. The job of the storefront application here is to allow customers to place orders for the candy, the application should not be concerned about the data source, it should not have to keep track of all the different data sources. This is where data repositories come in to play. Per the Repository Design Pattern, a public API is exposed via an interface and every consumer (our storefront application in this case) uses that API to talk to the data source. Which data source is being used or how its being connected to, these are not of concern to the application. The application is only concerned with the data it gets and the data it sends to be saved.

--ADVERTISEMENT--

Once the Repository Design Pattern is implemented, repositories can be created for each data source. The storefront application no longer would need to keep track of any data source, it just uses the repository API to get the data it needs.

Is it the magic bullet?

Well, no it is not. Like every design pattern it has its ups and downs, pros and cons.

Pros:

  • Separation of concerns; the application need not know about or track any or all data sources.
  • Allows easy unit testing as the repositories are bound to interfaces which are injected into classes at run time.
  • DRY (Dont Repeat Yourself) design, the code to query and fetch data from data source(s) is not repeated.

Cons:

  • Adds another layer of abstraction which adds a certain level of complexity making it an overkill for small applications.

How to go about it?

Let us see this with a little code example. I will use Laravel here in the example to leverage its excellent dependency injection feature. If you use any modern PHP framework then it should already have Dependency Injection/IoC container. Dependency Injection is required to implement Repository Design Pattern because without it you will not be able to bind a data repository to the repository interface and the whole idea is to code to an interface to avoid hard coupling. If you are not using any framework or your choice of framework does not have an IoC container then you can use an off the shelf IoC container (check footnotes).

Let’s crack on. Firstly, we set up our namespace and autoloading in Composer. Open up composer.json and add psr-4 autoloading for our namespace (in autoload node right after classmap).

    "autoload": {
        "classmap": [
            "app/commands",
            "app/controllers",
            "app/models",
            "app/database/migrations",
            "app/database/seeds",
            "app/tests/TestCase.php"
        ],
        "psr-4": {
            "RocketCandy\\": "app/RocketCandy"
        }
    },

After saving it, execute composer dump-autoload -o in the terminal to register autoloading for the new namespace. Create OrangeCandyRepository.php in app/RocketCandy/Repositories/OrangeCandyRepository/. This will be our repository interface.

<?php

namespace RocketCandy\Repositories\OrangeCandyRepository;

interface OrangeCandyRepository {

    public function get_list( $limit = 0, $skip = 0 );

    public function get_detail( $candy_id = 0 );

}

Now that we have an interface, we can create a repository. Create CityAOrangeCandyRepository.php in app/RocketCandy/Repositories/OrangeCandyRepository/.

<?php

namespace RocketCandy\Repositories\OrangeCandyRepository;

class CityAOrangeCandyRepository implements OrangeCandyRepository {

    public function get_list( $limit = 0, $skip = 0 ) {
        //query the data source and get the list of
        //candies
    }

    public function get_detail( $candy_id = 0 ) {
        //query the data source and get the details of
        //candies
    }

}

To bind CityAOrangeCandyRepository repository to the OrangeCandyRepository interface, we will leverage Laravel’s IoC container. Open up app/start/global.php and add following to the end of the file.

//OrangeCandyRepository
App::bind(
    'RocketCandy\Repositories\OrangeCandyRepository\OrangeCandyRepository',
    'RocketCandy\Repositories\OrangeCandyRepository\CityAOrangeCandyRepository'
);

Note: I have put the IoC binding in global.php only for demonstration. Ideally these should be put into a separate file of their own where you would put all the IoC bindings and load up that file here in global.php or you would create Service Providers to register each IoC binding. You can read more here.

Now we can go ahead and make use of the repository via the interface. In our CandyListingController.php located in app/controllers/.

<?php

use RocketCandy\Repositories\OrangeCandyRepository\OrangeCandyRepository;

class CandyListingController extends BaseController {

    /**
     * @var RocketCandy\Repositories\OrangeCandyRepository\OrangeCandyRepository
     */
    protected $_orange_candy;

    public function __construct( OrangeCandyRepository $orange_candy ) {
        $this->_orange_candy = $orange_candy;
    }

}

Here we inject the OrangeCandyRepository interface into our controller and store its object reference in a class variable which can now be used by any function in the controller to query data. Since we bound OrangeCandyRepository interface to the CityAOrangeCandyRepository repository, it will be as if we are directly using CityAOrangeCandyRepository repository itself.

So now, the type and kind of data source is the sole concern of CityAOrangeCandyRepository here. Our application knows only of OrangeCandyRepository interface and the API it exposes to which every repository implementing it must adhere to. The repository is resolved out of IoC container at run time, which means the interface <=> repository binding can be set as needed, the interface can be bound to any data repository and our application would not have to be concerned about the change in data source which can now be a database or a web service or a trans-dimensional hyper-data conduit.

One size does not fit all

As I mentioned above in the Cons of Repository Design Pattern, it adds a bit of complexity to the application. So if you are making a small application and you do not see it graduating to big leagues where more than one data source might be called into service, you will be better off not implementing this and sticking to good old data models. Knowing something is different than knowing when to use that thing. It is a very handy design pattern that saves a lot of headache both when creating an application and when that application has to be maintained or scaled up (or down) but it is not a magic bullet for every application.

I used Laravel specific code to demonstrate the implementation above, but it is fairly simple and similar with any decent IoC container. Got questions? Fire away in the comments below.

Footnotes:

Login or Create Account to Comment
Login Create Account
Recommended
Sponsors
Get the most important and interesting stories in tech. Straight to your inbox, daily.Is it good?