Repository Pattern

Goals

Concepts

Lesson

You briefly studied a mult-layered application when studying a program's command-line interface. The layers of a program help separate the various concerns needed to implement the program's functionality. Logic for interacting with the user, whether via a CLI or via a GUI, are placed in the presentation later, as you already learned. In a common three-tier application, the data layer is concerned with saving the information in some data store such as the database in the diagram below.

A three-tier application.
A three-tier application. Modified from diagram by Bartledan (Wikimedia Commons).

Repository Pattern

The term data store is more general than file system or database. An application may use various mechanisms to allow its data to persist even when the application is shut down or restarted. To allow the application to use various persistence mechanisms—some of which may not even be known at the time the application is first written—the repository pattern is used to abstract access to the data store behind a general repository interface.

Repositories generally work with some entities, which usually represent the business objects of the application. The specific methods a repository provides depends on the needs of the application and the types of data stores to be supported, but here are the fundamental functionalities provided by a repository:

Here is an example of a repository interface that might be used for accessing a fleet of vehicles for a transportation company. Can you identify the CRUD methods?

Repository for storing and retrieving vehicles.
/** A repository for persistent storage of vehicles. */
public interface VehicleRepository {

  /** Creates a new truck.
   * @param registrationNumber The license plate number.
   * @return The created truck.
   * @throws IOException if there is an error accessing the repository.
   */
  public Truck createTruck(@Nonnull final String registrationNumber) throws IOException;

  /** Creates a new van.
   * @param registrationNumber The license plate number.
   * @return The created van
   * @throws IOException if there is an error accessing the repository.
   */
  public Van createVan(@Nonnull final String registrationNumber) throws IOException;

  /** Retrieves all available vehicles.
   * @return All available vehicles.
   * @throws IOException if there is an error accessing the repository.
   */
  public Collection<Vehicle> getVehicles() throws IOException;

  /** Retrieves all available vehicles of a given type.
   * @param <V> The type of vehicles to return.
   * @param vehicleClass The class representing the type of vehicles
   * @return All available vehicles of the indicated type.
   * @throws IOException if there is an error accessing the repository.
   */
  public <V extends Vehicle> Collection<V> getVehiclesByType(@Nonnull final Class<V> vehicleClass)
      throws IOException;

  /** Looks up a vehicle by its license plate.
   * <p>The vehicle is returned as the expected type with no checks.</p>
   * @param <V> The type of vehicles to return.
   * @param registrationNumber The license plate number.
   * @return The vehicle with the given registration number, if any.
   * @throws IOException if there is an error accessing the repository.
   */
  public @Nullable <V extends Vehicle> V findVehicleByRegistrationNumber(@Nonnull final String registrationNumber)
      throws IOException;

  /** Saves a vehicle in the repository.
   * If a vehicle exists with the given registration number, it will be replaced.
   * @param vehicle The vehicle to save.
   * @throws IOException if there is an error accessing the repository.
   */
  public void saveVehicle(@Nonnull final Vehicle vehicle) throws IOException;

  /** Removes a vehicle from the repository.
   * <p>The default implementation delegates to {@link #deleteVehicleByRegistrationNumber(String)}.</p>
   * @param vehicle The vehicle to delete.
   * @throws IOException if there is an error accessing the repository.
   * @see Vehicle#getRegistrationNumber()
   */
  public default void deleteVehicle(@Nonnull final Vehicle vehicle) throws IOException {
    deleteVehicleByRegistrationNumber(vehicle.getRegistrationNumber());
  }

  /** Removes a vehicle from the repository based upon its registration number.
   * @param registrationNumber The license plate number of the vehicle to delete.
   * @throws IOException if there is an error accessing the repository.
   */
  public void deleteVehicleByRegistrationNumber(@Nonnull final String registrationNumber) throws IOException;

}

Review

Gotchas

Self Evaluation

Task

Your booker project currently has a lot of code for working with publications directly in the Booker application class. Even the code for creating the publications is there. In the future you may want to load these publications from a database, or retrieve them directly from the Internet in real time.

Create and implement a repository for your publications.

  1. Refactor your persistence-related publication methods into a PublicationRepository interface.
    • Include only those methods you need now and/or think appropriate. Don't try to plan ahead too much about future needs.
  2. Move your existing implementation of those methods into a SnapshotPublicationRepository to hold a snapshot of the popular publications on a historical date (i.e. those publications you have been working with since creating the Booker application).
    • This repository doesn't actually access any data storage.
    • The collection of publications should no longer be a static variable; construct whatever collections you need as an instance variable when the repository is created.
    • The SnapshotPublicationRepository is to be a read-only repository! Even if the PublicationRepository interface provides for modification, the SnapshotPublicationRepository implementation must not allow such methods to be called. Consider how read-only collections are implemented.
  3. Create a unit test of your repository implementation.
    • Many if not all of your tests will already be written in some form; they merely need to be moved out of the Booker unit test class and into a repository unit test class.
    • This is a refactoring effort. There should be no change in the main program functionality. You are only changing the program structure and model to be more easily maintained and to support new features.

See Also

References

Acknowledgments