Repository pattern with Ruby on Rails: Decoupling ActiveRecord and persistence layer

Repository pattern with Ruby on Rails: Decoupling ActiveRecord and persistence layerLaerti PapaBlockedUnblockFollowFollowingMar 10IntroThe Repository pattern is a way of working with a data source.

In the book Patterns of Enterprise Application Architecture, Martin Fowler describes a repository as follows:A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory.

Client objects declaratively build queries and send them to the repositories for answers.

Conceptually, a repository encapsulates a set of objects stored in the database and operations that can be performed on them, providing a way that is closer to the persistence layer.

Repositories, also, support the purpose of separating, clearly and in one direction, the dependency between the work domain and the data allocation or mapping.

In other words it decouples our business domain logic from the persistence layer.

Some of the benefits using it are the following:Increases testability and loose coupling with our persistence layer.

Easier to test your application logic since we test logic and not infrastructure (persistence layer).

We test our business logic in isolation from external dependencies.

Easier to persist a domain entity since each repository implementation knows how to save the entity itself and its underlying relationships.

We have an abstraction without caring about our persistence layer which yields to better separation of concerns since persisting domain entities is handled by each concrete repository.

Each domain entity is focused exclusively in the domain model itself and its specific domain logic.

Better object oriented design since each repository encapsulates a collection of objects that is responsible to perform actions on them.

Improves code maintainability and readability by separating business logic from data or service access logic.

A simplified class diagram can be found in the figure below.

Repository pattern diagramIn order to simplify things we will take a simpler approach by removing Factory and Criteria interfaces.

Both of them deserve an article itself.

In the next article we will introduce mappings between our data layer and our domain entities layer.

Dependency InjectionDependency injection is a strong and major thing in static typed languages like Java.

It connects components together at runtime providing loose coupling between them and yields to better testable code since tests depends on interfaces and not implementation details.

A simple example can be seen below where we pass a post to another object’s constructor signature.

class SomeServiceHandler def initialize(repository) @repository = repository end def call .

endendBut that’s a simple example where the client initiates an object and passes the repository instance to it.

What happens in a different context and environment?.The real problem comes when the same instance is used in different context by different clients.

In order to avoid the dependency’s reference you need a way to reference it at runtime without referencing it’s actual class name.

For example the following is a code snippet that I came across a couple of months ago:class SomeService def self.

client if Rails.

env.

test?.MockTestClient.

new else RealClient.

new end endendWhat we want to achieve in the end is to reference our dependency without having to change or type it in a different context or place.

What most frameworks do is that they provide a configuration where a centralised place exists for all our objects.

How they should be initialised it’s up to developers responsibility to provide it.

Dependency injection is a strong technique used to increase efficiency and modularity.

Each technology has each own dependency injection framework but all of them serve the same purpose: increase efficiency and modularity.

There are a couple of ways to implement it in Ruby.

dry-rb project has dry-container intended to be a middleware for dependency injection implementations.

For more details please have a look on the related documentation.

Other approaches include registries (which we will follow in this article) or different configurations per environment.

Being a big fan of explicit vs implicit we will start by declaring each dependency per environment.

It’s a simple and easy to understand paradigm where you don’t have to keep your mind questioning why and how this works or putting effort on learning any kind of library or framework.

It’s easy to implement and you avoid adding another dependency to your project which I also think that it is a big benefit itself.

Repository definition via a RegistryOne simple method that one can follow is to keep a registry of all the repositories of the application and register each one of them depending on the context that we are into.

In our example below we define two set of repositories.

One collection includes all repository definitions that will be used in our test environment and another collection includes all our repositories that will be used in our non test environment.

Test repositories will have an in memory persistence layer implementation which will provide us with fast running test.

Persistence layer can be set to the actual database implementation in our acceptance or functional tests.

Let’s see an example in Rails where we define and register our repository implementations.

# file environment.

rbNON_TEST_REPOS = { products: MoviesRepository.

new(gateway: Product), recos: RecosRepository.

new(gateway: RecosService.

instance) # .

}TEST_REPOS = { products: InMemoryTestRepository.

new(gateway: InMemoryPersistence.

new), recos: InMemoryTestRepository.

new(gateway: RecosMockService.

new) # .

}if Rails.

env.

test?.RepositoryRegistry.

register_many(TEST_REPOS) else RepositoryRegistry.

register_many(NON_TEST_REPOS)endIgnoring the implementation details about the registry for now we register our dependencies per environment.

At the above example test environment will have our in memory persistence repositories so it will be database agnostic and all non test environments will use the implementation with the database persistence.

Below we describe a possible implementation for the RepositoryRegistry:# file repository_registry.

rbclass RepositoryRegistry RepositoryNotFound = Class.

new(StandardError) def self.

register(type, repo) repositories[type] = repo end def self.

register_many(collection = {}) collection.

each { |name, repo| register(name, repo) } end def self.

for(type) repositories.

fetch(type) do raise(RepositoryNotFound, "Repository #{type} not registered") end end def self.

repositories @repositories ||= {} end private_class_method :repositories private def initialize(*) raise "Should not be initialiazed" endendPretty simple.

A registry to hold all our registered repositories that can be accessed by calling the for class method.

Before seeing the implementation for each concrete repository let’s see an example of a service that will use the registry to store some products.

class ProductsServiceHandler attr_reader :products_repo def initialize(products_repo = RepositoryRegistry.

for(:products)) @products_repo = products_repo end def add_product(params) products_repo.

add(name: params.

name, description: params.

description, price: params.

price) end def rate_product(params) product = products_repo.

find(params.

product_id) product.

rate(command.

user_id, params.

rating) repository.

save(product) end # def add_to_inventory .

endIn the above example we see that ProductsServiceHanlder does not hold any reference to ActiveRecord Product model neither has any knowledge on how the product will be persisted.

It only holds a reference to the ProductsRepository that will be responsible to add and save a product.

The above can be extended with helper module to inject our repositories in a different way.

For example:class MyService include RepoContainer.

new(:products) def call(params) products_repo.

add(params) endendSince I would like to keep things simple I will avoid the definition of the container.

Below we describe a possible solution on how to implement each concrete repository and how to isolate ActiveRecord persistence layer behind each repository.

First we will introduce ApplicationRepository class that will be the parent for each concrete repository implementation.

The parent class will define some CRUD methods that can be shared across all repositories like add, save, delete etc.

This is not necessarily but it will DRY things up and will be useful as we will see later on.

class ApplicationRepository def initialize(gateway:) @gateway = gateway end def add!(params = {}) gateway.

create!(params) end def find(id) gateway.

find(id) end # .

private attr_reader :gatewayendIt’s pretty simple since it delegates most of the work to the gateway.

The gateway would be the database context and in our case recall the we created each repository by defining an active record class as the gateway.

This is pretty powerful since now in each concrete repository implementation we can take advantage of ActiveRecord API to build super thin repositories:# recall in environment.

rb.

The gateway injected in the creation of the repository is an ActiveRecord Product class.

MoviesRepository.

new(gateway: Product)class ProductsRepository < ApplicationRepositoryendThere may be occasions where a subclass may not have any extra methods defined if the methods in the parent class are sufficient.

In the example above the ProductsRepository does not define any new methods since it inherits them from its parent class.

In that case we can avoid creating a new empty class but instead keeping the application repository which acts as a generic delegator to our storage.

For exampleRepositoryRegistry.

register( products: ApplicationRepository.

new(gateway: Product))NOTE: The above implementation is not complete since we did not define the mapping between our active record models and our domain entities.

The next article will continue from where we left and we will introduce our domain entities which will be responsible to model and contain our domain business logic.

Each active record instance will be decoupled from our domain layer and will only serve as data access objects which will provide the interface to our persistence mechanism.

SummarySo far we have decoupled active record and persistence knowledge from our application business use case.

Persistence layer acts as implementation details in our application.

Using the repository pattern it is easier to maintain centralized data access logic and we achieved our initial intention, to create an abstraction layer between the data access layer and the business logic layer.

Last but not least we saw that dependency injection gives our code a higher loose coupling and by this more flexibility for changes as well as more testable code.

References[1] https://martinfowler.

com/eaaCatalog/repository.

html[2] https://deviq.

com/repository-pattern/[3] https://www.

amazon.

com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/.. More details

Leave a Reply