Repository Pattern
Goals
- Learn about the repository design pattern for persistence management.
Concepts
- CRUD
- data store
- Data Access Object (DAO)
- entity
- persistence
- repository pattern
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.
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:
- The ability to create new entities.
- The ability to read the entities. This may involve querying for specific types of entities, or find an entity with a specific identifier.
- The ability to update or change the entities.
- The ability to delete entities when they are no longer needed.
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?
/** 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
- Don't put implementation assumptions into the repository contract.
Self Evaluation
- What purpose does a repository serve?
- What are some different examples of how a repository might be implemented?
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.
- 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.
- Move your existing implementation of those methods into a
SnapshotPublicationRepository
to hold asnapshot
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 thePublicationRepository
interface provides for modification, theSnapshotPublicationRepository
implementation must not allow such methods to be called. Consider how read-only collections are implemented.
- 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
- Three-tier application diagram modified from diagram by Bartledan (talk), based on a file by User:Foofy [Public domain], via Wikimedia Commons.
- Some symbols are from Font Awesome by Dave Gandy.