To be clear, this is not about swapping out Ecto.Repo for another Repo implementation, so perhaps repository is not the best name. Its about having a behavior for your data entity that is satisfied by a concrete implementation that uses Ecto and another that could use a List.
On Sunday, 12 June 2016 21:27:49 UTC+3, Larry Weya wrote: > > As always, thanks for taking the time to reply. > > On Sunday, 12 June 2016 13:16:17 UTC+3, José Valim wrote: >> >> I believe the question you need to ask yourself is of you really need >> this abstraction in the first place. You are now using a new platform but >> you are worried about concerns that are more relevant in the platform you >> originally came from. > > > Its not really a concern that was only relevant for the other platform > (its in fact not built in and had to build it in), its just a general > design pattern thats meant as a guide so you don't have e.g. queries that > do the same things duplicated across the codebase. So instead of multiple > controller functions composing different variations of the "get active" > query, you have in a central place. > >> >> First of all, I don't believe in swapping the implementation in tests as >> a general pattern/feature. Every time you swap, you still need to write an >> integration test to guarantee the two layers work together, so what are you >> gaining? If you are going to swap only in some cases, I would rather define >> proper contracts for those particular cases instead of swapping an >> implementation detail (I wrote about this in Plataformatec blog, I am on my >> phone right now but the digest is that, for an Twitter http client, you >> mock the "get_tweets" functionality and not the Http client itself). >> > > I had read the article and coincidentally I was mocking http requests and > having a difficult time. The article guided my design into implementing a > test-only interface that exposed the required functionality. The article > actually also also guided part of my repository implementation and I > believe its in the same spirit where when testing a controller function, I > can use an in-memory repository that satisfies the interface and use the > Ecto based repository in production. The whole point of the abstraction for > me is to keep the implementation details of how data is stored/retrieved > outside of my controller functions. > > >> Similarly concerns like caching and tests performance are going to hit >> you much later in Elixir and you can still provide that at the Ecto >> repository level instead of having to write your own user repositories. >> > > Performance, especially within tests is not as much of a concern as > simplicity and I would argue that for the same reason you would have an > in-memory Twitter client, you could have an in-memory repository within > your tests. > > >> It is really common for OO developers to think of Elixir modules as the >> minimal code structure (and therefore breaking everything into modules) and >> forget about functions. My advice would be to think of those as simply >> regular functions that are grouped based on functionality rather than on >> structure, for example MyApp.Accounts.get_active_users. >> > > I think this is an approach that could have the same result, keeping the > underlying queries and Ecto interactions outside of the controllers (and > other layers where we want to control how the data is accessed). I do > however feel that the repository pattern provides a convention on how the > code should be structured but perhaps my implementation is a bit > over-engineered. I will go back and look at how it can be simplified. > >> >> On Sunday, June 12, 2016, Larry Weya <[email protected]> wrote: >> >>> I'm currently re-implementing part of a Laravel(PHP) codebase in Elixir >>> where I had used the repository pattern >>> <http://blog.gauffin.org/2013/01/repository-pattern-done-right/> 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. >>> >>> >>> -- >>> You received this message because you are subscribed to the Google >>> Groups "elixir-lang-talk" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to [email protected]. >>> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/elixir-lang-talk/47df7f07-0e79-40d7-b551-86839dba22b3%40googlegroups.com >>> >>> <https://groups.google.com/d/msgid/elixir-lang-talk/47df7f07-0e79-40d7-b551-86839dba22b3%40googlegroups.com?utm_medium=email&utm_source=footer> >>> . >>> For more options, visit https://groups.google.com/d/optout. >>> >> >> >> -- >> >> >> *José Valim* >> www.plataformatec.com.br >> Skype: jv.ptec >> Founder and Director of R&D >> >> -- You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/f9029c8b-f437-4552-ac27-70b4f8e61877%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.
