• 6 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 4/15/20

Streamline Your Code Using the Repository Pattern

 In this chapter, you'll see how what we have done so far can be expressed as a pattern, which are commonly used designs. 😁 We'll start by looking at the facade pattern, then move on to the repository pattern, which is a type of facade pattern. Towards the end of the chapter, we'll look at how you can use factories and generics to make your code easier to use.  

Ready? Let's dive in!

Using Patterns in Design

Over the previous chapters, you have seen a variety of persistence mechanisms. As time went on, you repeated similar code functions: initializing, creating, reading, updating, and deleting. You sometimes wrote a similar boilerplate code where the same sequence of actions was coded for different object types.

When you carry out similar tasks not easily factored into an object, you should look to see what patterns from which you can borrow. Patterns are descriptions of common relationships and interactions between system components. 

Let's look at a real-life example: a dress pattern is a set of paper shapes that can be copied, pinned to material, and the material cut around the shapes to create the dress panels:

They are not the dress itself, but the guide for making the dress.

In this way, design patterns are a master guide that captures the current best practice. You can see any improvements to the pattern in future uses, and it becomes the reference point for future improvement discussions.

Your experience with persistence so far suggests two patterns that can help keep your code clean even if you use several different persistence mechanisms: the facade and the repository.

Understanding the Facade Pattern

Facades are essentially a front that you put on something to hide the underlying complexity. In the previous exercises, we created persister objects that wrap up the complexity of managing files and properties in reasonably simple CRUD methods.

So...we've already used this pattern? 😀

Yes! Going further, by using a single facade, you can reduce the entangling or coupling of the application with the persistence code by making sure that all persistence activities happen behind the facade.

In our robot parts shop application, different parts of it want to update customer details, and so make JDBC calls. If the code in those places makes those calls directly, it becomes challenging to switch over to a different mechanism. You would have to find all the different places in the application that use them. And if you miss some, your application fails. 😬

By using a facade, all the JDBC calls are made behind the facade, and the application only uses a small number of defined methods. This means only that code behind the facade has to be modified if the persistence mechanism is changed. Because the facade is an interface that you program the application to use, it is also an example of an application programming interface (API).

Decoupled application and persistence layer, using a Facade API
Decoupled application and persistence layer, using a facade API

Cool! How do we use this? 

The repository pattern is a popular facade pattern for a set of implementations with common persistence features. Let's check it out!

Applying the Repository Pattern

The repository pattern consists of three aspects:

  1. An interface definition that declares the persistence methods. This is usually a set of CRUD calls. 

  2. A set of facade classes that implement the interface and hide (encapsulate) the complexity of persistence behind simple CRUD calls.

  3. A factory method (or injection) to select the appropriate persister for the application.

Let's see how this plays out in our customer database with reworking what we've done so far into a repository pattern form:

Let's look at this in more detail:

Step 1: First write the interface that defines the application programming interface. For the robot shop's customer list, this can be a set of CRUD operations using person objects, as follows:

public interface CustomerRepository {

    public void create(Person customer);
    public Person read(int id);
    public void update(Person customer);
    public void delete(Person customer);
    
}

Step 2: Create at least one implementation of this. There is already a Hibernate persister that stores person objects in the customer SQLite database for the shop. You can modify the method names so that they match the CustomerRepository interface, as follows:

public class HibernatePersonPersister implements CustomerRepository {

    protected Session session = null;

    /** Creates a registry from the configuration file, uses that to build a session factory, and then creates a session to use to communicate with the database */
    public HibernatePersonPersister() {

        File configFile = new File("hibernate.cfg.xml");
        
        final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                .configure(configFile) 
                .build();

        sessionFactory = new MetadataSources(registry)
                .buildMetadata()
                .buildSessionFactory();
                
        session = sessionFactory.openSession();

    }
    
    /** Implementes the 'Create' method of the CRUD interface. For hibernate this is the same as the update, so delegates to that
     */
    @Override
    public void create(Person aCustomer) {
        update(aCustomer);
    }
    
    /** Implements the 'Read' method of the CRUD interface,
     * returning the database entry that has the given ID */
    @Override
    public Person read(int id) {
        session.beginTransaction();
        Person customer = session.find(Person.class,  id);
        session.getTransaction().commit();
        return customer;
    }

    /** Implements the 'Update' method of the CRUD interface,
     * copying (or inserting) the given customer into the 
     * database */
    @Override
    public void update(Person aCustomer) {
        session.beginTransaction();
        session.save( aCustomer ) ;
        session.getTransaction().commit();
    }

    /** Implements the 'Delete' method of the CRUD interface,
     * removing the given customer from the database */
    @Override
    public void delete(Person aCustomer) {
        session.beginTransaction();
        session.delete( aCustomer ) ;
        session.getTransaction().commit();
    }

}

Now we have a customer CRUD interface and an implementation.

As with other interfaces, you can use the customer CRUD interface in most of your application code without knowing or caring about how it is implemented. In the following code, only the constructor in Line 1 specifies the implementation class above (HibernatePersonPersister). The rest just uses the interface CustomerRepository:

CustomerRepository customers = new HibernatePersonPersister();

//read the first customer
Person customer = customers.read(1);

//change their address
customer.address = "A new address";

//update the customer database
customers.update(customer);

If you want to use a different persistence technology (say, JAXB), you can write a different persister implementation, and construct that instead - and not change any other line of code. Here is a completely different persister:

CustomerRepository customers = new CompletelyDifferentPersister();

//read the first customer
Person customer = customers.read(1);

//change their address
customer.address = "A new address";

//update the customer database
customers.update(customer);

You can see here that only the first line has changed. All the rest of the code can stay the same, even though you are using a completely different persistence technology.

You are decoupling your code from the library through an API. The CustomerRepository is the API, and by writing your application code to use this, you can change the implementation without changing very much application code.

But we still have to hard code the constructor. How else might we tell the application which implementation to use?

There are two common ways to do this. The first is dependency injection; you pass the implementation into the classes that use it as a constructor argument. The second is to use the factory pattern: you ask a particular class to make the right implementation for you.

Each of these has different costs and benefits:

  • Dependency injection means your code need only import the interface - it doesn't even have to  know about a factory. However, when you are injecting many dependencies through application constructors, those constructors can get quite unwieldy. 

  • The factory approach gives you more control, but your code now depends on the factory.

Let's look at some examples.

Using Method 1: Dependency Injection

Injection means that you pass in (inject) implementation dependencies from an external configurator. For instance, you might have a UI class that displays customers on a screen, but you don't want to have to hard code which repository implementation it uses. Instead, you inject the right repository as an argument in the constructor, like this:

public class CustomerListView extends JList<Person> {

    CustomerRepository store = null;
    
    public CustomerListView(CustomerRepository aStore) {
        this.store = aStore;
    }
    
    public void saveButtonPressed() {
        
        Person editedPerson = new Person();
        
        //...copy person details from screen... 
        
        store.create(editedPerson);
    }
}

In the example above, there are only references to the interface. You can change the persistence technology without changing this code at all.

Using Method 2: Factories

The factory pattern attempts to decouple your application from your persistence library by creating an extra step between requesting an implementation, and its construction. 

Essentially, your application code asks the factory to make an implementation, and the factory does so. Your code does not decide which implementation to make; the factory does. In the above examples, the application code included something like:

CustomerRepository = new HibernatePersonPersister();

So if you wanted to use a different implementation, you would have to find all the places in the code that constructed the old one and replace it. With a factory, instead, the code asks the factory, and the factory returns an instance:

CustomerRepository = CustomerRepositoryFactory.getRepository();

Now you only have one place to change - or configure - which implementation is returned: inside the factory. If you decide to use a different persister in the future, you only need to change this factory to return the new one - and any applications that use the factory gets the new persister.

For example, you might have this factory class; it has static methods, so you don't have to create an instance of it:

public class CustomerRepositoryFactory {

    private static CustomerRepository repository = null;
    
    public static CustomerRepository getRepository() {
        if (repository == null) {
            repository = new JdbcCustomerPersister();
        }
        return repository;
    }
}

As an example of using this in an application, say you have a save button on the user interface; you would create a person object, load the details from the entry fields, ask the factory for a repository, and use that to store it. Like this:

public class CustomerListView extends JList<Person> {

    public void saveButtonPressed() {
        
        Person editedPerson = new Person();
        
        //...copy person details from screen... 
        
        CustomerRepository repository = CustomerRepositoryFactory.getRepository();
            
        repository.create(editedPerson);
    }
}

The persistence implementation was set in factory code, and so is fixed at compile time. You change the factory code to change which implementation is created.

The factory could also load a configuration file and look at a property to decide which implementation to create. You might say that if a property customerstore was set to SQLite, it would create a PersonSQLitePersister. Or, if set to JAXB, it would create a PersonJaxbPersister.

To be flexible enough to configure the factory to use implementations you haven't thought of yet, you could specify the implementing class in a configuration file or an environment variable. For example, you might have an environment variable set like this: 

customerstore=com.openclassrooms.persistence.HibernatePersonPersister

The factory would read that property and create that class, like this:

public class CustomerRepositoryFactory {

    private static CustomerRepository repository = null;
    
    public static synchronized CustomerRepository getRepository() {
        if (repository == null) {
            repository = makeRepository();
        }
        return repository;
    }
    
    public static CustomerRepository makeRepository() {
        //get the persister class name from an environment variable
        String persisterClassName = System.getenv("customerstore");
        try {
            Class persisterClass = Class.forName(persisterClassName);
            
            return (CustomerRepository) persisterClass.newInstance();
        } catch (Exception e) {
            System.out.println(e+" attempting to create persister "+persisterClassName);
            e.printStackTrace();
        }
        return null;

    }
}

This reads the system environment variable customerstore to get the class name of the repository implementation, and then instantiates that to use as the CustomerRepository.

Let's say someone writes a specialist fast-performance customer repository. They create a new class that implements CustomerRepository and uses fast-performance technology, calling it org.mystuff.FastCustomerRepository. Now they can set the environment variable as follows:

customerstore=org.mystuff.FastCustomerRepository

The factory uses their new class without having to change the application code or the factory code.

Whichever factory implementation you choose, your application code needs to use the factory to get the repository implementation, and the application does not need to concern itself with the persistence mechanism. This limited set of contact points with the persistence library stops the application code from getting tangled up with the persistence code.

Using factories is particularly useful when testing. You can create an uncomplicated persister with a simple, small database using the same contents, so you have a known dataset to run unit tests against. This known dataset is called a test database, a dummy database, a mock database, or part of a test harness. We will cover this in the next chapter.

Reducing Repository Code Using Generics

You can reduce the amount of code you need to write even more by using Java's Generics. These can specify the relevant type in a repository interface where it is used, rather than where it is defined (see the Oracle tutorial to see how to use these). 

Although patterns should be considered a set of guiding principles and not as a code abstraction, with generics, you can write code that very nearly implements a general repository. Java Generics provide a way of generally typing definitions. Consider our customer CRUD repository definition:

public interface CustomerRepository { 
    public void create(Person customer);
    public Person read(String id);
    public void update(Person customer);
    public void delete(Person customer); 
}

Other CRUD repository definitions would be pretty much the same, the only difference being the type of persisted object. A robot part repository would look similar, but with person replaced by RobotPart:

public interface RobotPartRepository { 
    public void create(RobotPart part);
    public RobotPart read(String id);
    public void update(RobotPart part);
    public void delete(RobotPart part); 
}

Every other interface would look very similar, only with different persisted object types. With generics, you can make this definition, well, generic:

public interface Repository<DataObject> { 
    public void create(DataObject data);
    public DataObject read(String id);
    public void update(DataObject data);
    public void delete(DataObject data); 
}

Now instead of having to write a repository definition for every data object you want to persist, you only have to write this one. Specify which object is relevant at the point you are using the interface rather than when writing it.  

In the code above, where we used the CustomerRepository, instead use the repository typed with person (compare the first lines):

Repository<Person> customers = new HibernatePersonPersister();

//read the first customer
Person customer = customers.read(1);

//change their address
customer.address = "A new address";

//update the customer database
customers.updat(customer);

And similarly, where you implement the interface, you type it. In the generic form, for example, the HibernatePersonPersister would implement repository typed with a person like this:

public class HibernatePersonPersister implements Repository<Person> {
    

Ta-da! ✨

Try It Out for Yourself!

In this chapter, you learned about customer database persisters. You have been leading the way on the robot parts catalog; now is your opportunity to create a robot part persister based on the repository pattern. You'll need to write an interface and at least one implementation. As a stretch, write a factory that creates your implementation.  💪

Let's Recap!

  • You can model your library of persister classes on:

    • The facade pattern, which serves to hide complexity from application code. 

    • The repository pattern, which is a type of facade pattern used to hide persistence complexity. 

  • The factory pattern makes it easier to switch between repositories, and therefore between persistence methods. You can also use the factory pattern aspect of the repository pattern to centralize which implementation of a persister your application uses.

All of this makes it relatively simple to swap different implementations in and out, helping to future proof your application, making it easier to swap in different test databases.

Let's get to testing our database in the next chapter.

Ever considered an OpenClassrooms diploma?
  • Up to 100% of your training program funded
  • Flexible start date
  • Career-focused projects
  • Individual mentoring
Find the training program and funding option that suits you best
Example of certificate of achievement
Example of certificate of achievement