I'm currently re-implementing part of a Laravel(PHP) codebase in Elixir where I had used the
repository pattern in only some of the data models. In the Elixir implementation, I'd like to use it across all the data models mainly because
1. I can swap out the implementation within tests
2. I can swap out the implementation to e.g. go through a cache layer for reads
3. I want to able to use different ecto repos for e.g. read and write and having abstract repositories would allow this to be done from a central place
I worked on an initial implementation this weekend that works like this.
For each data entity, implement a "behaviour" with callbacks for that particular entity e.g. a UserRepository where you need to query for a list of active users would look like this
defmodule UserRepository do
use Repository.Base, # parses opts and config and sets @repository to configured repository module
otp_app: :my_app
repository: MyApp.Repositories.EctoUserRepository # this points to the concrete implementation and should be set within the config
@callback save(Ecto.Changeset.t) :: {:ok, User.t} | {:error, Ecto.Changeset.t}
@callback get_active(non_neg_integer, non_neg_integer) :: [User.t]
end
The Ecto backed repository implementation would look like this
defmodule EctoUserRepository do
use Repository.Ecto.Base, # parses the opts and config, sets @repo and @read_repo and implements a save function thats common to all Ecto repos
repo: MyApp.Repo,
read_repo: MyApp.Repo # could set a different repo for reads
@behaviour UserRepository
import Ecto.Query, only: [from: 2]
def get_active(offset, limit) do
(from u in User,
where: u.active == true,
offset: ^offset,
limit: ^limit)
|> @read_repo.all()
end
end
Please share any thoughts on this.
Now this is where I need some help, I want to able to use the repository as
UserRepository.get_active(0, 20)
what I've done at the moment is redefine each function within the UserRepository and delegate to whatever repository is configured
defmodule UserRepository do
...
@callback save(Ecto.Changeset.t) :: {:ok, User.t} | {:error, Ecto.Changeset.t}
@callback get_active(non_neg_integer, non_neg_integer) :: [User.t]
def save(changeset) do
@repository.save(changeset)
end
def get_active(offset, limit) do
@repository.get_active(offset, limit)
end
end
I find that this is quite repetitive and forces the same for all repository implementations and functions.