Repository Pattern não precisa ser chato, principalmente com Laravel

Vinicius Reis
by Vinicius Reis
Published in
5 min readMar 5, 2016

Na comunidade PHP e em especial na comunidade Laravel, muito se tem falado do Repository Design Pattern. Minha intenção é desmistificar muita coisa em volta desse modelo de desenvolvimento.

Um repositório é um serviço

Isso mesmo que você leu, repositórios fazem parte da camada de serviços de um sistema. Eles são uma classe/categoria de serviço. Quando se esta estudando design patterns esbarramos muito em services, se você entendeu bem como serviços funcionam sabe que é neles que concentramos nossas regras de negócio.

Quando falamos de regras de negócio também estamos falando de acesso a dados e/ou entidades, que basicamente são nossos models. Repositórios existem para que o acesso e interação com nossos models não seja feito diretamente em outras partes do sistema como controllers.

Repositórios são os serviços da camada de acesso e interação com as entidades do banco de dados.

Qual o objetivo real de um repositório?

Antes de ir para a parte prática, existe um pouco mais de teoria em repositórios. Basicamente existem objetivos que “devem” ser alcançados quando usamos repositórios.

  • Centralizar regras de recuperação e persistência de dados.
  • Abstrair a utilização de ORMs possibilitando a troca por outros ORMs
  • Abstrair o tipo de banco de dados, que assim como ORMs pode ser trocado.
  • Devolver dados em um formato agnóstico (array ou StdClass), deixando zero acoplamento de ORM.

Lindo, não? Porém a vida real te dá limões, e com limões se faz uma limonada.

No mundo real, dificilmente você muda de ORM no meio do projeto, muito menos de banco de dados, já que a maioria dos ORMs já dão suporte a essa troca de banco de dados. O maior problema seria trocar de “paradigma” de banco de dados, de relacional para não relacional (NoSQL).

Quantas vezes você trocou um banco de dados do projeto? Quantas vezes você trocou de ORM em um projeto? Quantas vezes você migrou de MySQL/PostgreSQL para MongoDB?

Eu apostaria que na maioria esmagadora dos projetos nenhuma dessas situações aconteceu, das vezes que isso aconteceu apostaria que a troca para um banco de dados NoSQL é quase inexistente.

Se isso é uma realidade, porque então usar repositórios???

Repositórios em projetos do mundo real

Mesmo que a maioria esmagadora dos projetos não “precise” de uma camada de abstração tão grande no que tange acesso a entidades do nosso banco de dados, ainda há muita utilidade para esse design pattern.

Model, View e Controller

Em algum momento ou outro da sua vida como desenvolvedor você aprendeu MVC. Com isso você aprendeu o que são os Models um jeito muito legal e (por que não?) eficiente de acessar os dados de uma database.

Com o tempo essa “maravilha” também se tornou um problema, muitas regras de negócio (leitura e escrita) acabam ficando espalhadas ou melhor jogadas…

Acima de qualquer conceito ou filosofia que repositórios possam ter eles devem agregar valor e qualidade ao projeto. Isso se traduz em facilidade de manutenção!

Como começar a usar repositórios

Sabe criar uma classe? Então você está apto a criar um repositório.

Sem firulas ou “boas práticas”, este código é apenas para provar um ponto: Não precisa de muito para criar uma classe de repositório.

Somente após o conceito ser entendido podemos falar de S.O.L.I.D., DRY, PSRs ou Clean Code.

Agora vamos ao Laravel…

O Laravel dispensa apresentações e o Eloquent (seu ORM) também, há muita documentação e conteúdo sobre isso. O ponto que importa aqui é organização.

Como dito anteriormente, usar nosso model diretamente pode (e provavelmente vai) espalhar nossas regras ao vento. Os repositórios existem para resolver esse problema. Por isso partimos da premissa que:

Somente no repositório executaremos consultas e interações no banco de dados

Com isso em mente e a ideia de que não precisamos ser “conservadores” podemos fazer MUITAS coisas.

Seja genérico antes de ser especifico

Um repositório seria muito parecido com isso:

O problema dessa abordagem é basicamente o reúso, toda nova implementação precisa “copiar, colar e modificar” quando poderia ser apenas “estender e modificar”.

Para isso precisamos aprender alguns truques que o Laravel e o Eloquent nos proporcionam.

Eloquent de modo dinâmico

O primeiro é como o Eloquent funciona internamente, porém sem entrar em muitos detalhes (mais informações em um post futuro).

Maneiras diversas de se criar e executar uma query com eloquent

Em resumo e indo contra o que muitos argumentam, quando você “invoca” determinados métodos em um model Eloquent, ele cria um novo objeto query builder (Eloquent\Builder), e é com ele que você concatena seus argumentos, até executar a query e obter o retorno da mesma.

Isso abre muitas possibilidades, como mostrado na linha 15 do exemplo, eu crio (e descarto) um objeto Eloquent, apenas para obter o objeto Eloquent\Builder, sem em nenhum momento usar uma chamada estática.

Criando um novo objeto Eloquent\Builder dinamicamente

Como podem ver, usando o Container do Laravel é ainda mais fácil criar um objeto Eloquent\Builder.

Criando nossa abstração

Agora você tem todo o conhecimento necessário para criar uma base sólida de repositório, que atenda as suas necessidades. Vamos ver como seria um exemplo simples de como usar tudo isso a seu favor.

Nossos repositórios ficariam assim:

Repositório simples
Repositório com relacionamentos

Este é um exemplo extremamente simples, não há limites para as combinações que você pode criar. Há ainda mais abstrações possíveis, você pode ter toda uma metodologia de crud que te permita extrema liberdade e versatilidade.
Por exemplo, no warehouse existe uma classe base para crud, com vários pedaços dela separados. Não são todos os repositórios que usam crud, e muitos deles dependem de mais coisas, por isso o warehouse não insere métodos crud por padrão, você precisa importar esses métodos manualmente para seu repositório.

Se você possui uma base sólida e distribuída de abstração o nível de reaproveitamento de código tende ao infinito.

Por exemplo, em um sistema multi-tenant existem duas áreas, a administrativa, onde se tem acesso a todos as entidades de todos os tenants. Porém, quando um usuário comum acessa a área de um tenant (tenants podem ter vários usuários associados a si) ele só pode ter acesso e criar coisas que pertençam ao seu tenant.

A área administrativa já possui uma série de repositórios que por sua vez tem muitos métodos que são reaproveitados na área do tenant. Para não reimplementar todos esses repositórios novamente e não criar monstrinhos, cria-se repositórios que estendem os repositórios originais. Assim por herança eles possuem os mesmos métodos que os repositórios de administração. E como toda consulta é feita criando um novo objeto Eloquent\Builder pelo método newQuery basta modificar o comportamento desse método.
A mesma técnica pode ser aplicada quando se cria uma nova entidade, definindo o tenant_id dela automaticamente. Para deixar as coisas mais fáceis ainda, toda essa lógica é feita em um trait. O resultado você confere abaixo.

Como você pode ver, modificar o comportamento de um repositório, e até mesmo qualquer classe se torna algo simples se pensamos primeiro na abstração e depois na implementação.

Este post ficou bem maior do que eu esperava, porém não quis dividir ele em duas partes, pois poderia perder parte do foco. Ainda há mais conteúdo a ser tratado como por exemplo iterfaces e bind de classes, porém isso terá que ficar para outro momento.

That’s all, folks!

--

--

Vinicius Reis
by Vinicius Reis

Fiquei sem meus peões, meu cavalo, minha torre, meu bispo… E até a rainha… Mas ainda é muito cedo para um xeque-mate. Roy Mustang — Fullmetal Alchemist