Utilizando uma Arquitetura de Camadas no Desenvolvimento Laravel
Sou um grande adepto da adoção de padronização para qualquer coisa que se for fazer durante o desenvolvimento, desde a criação da estrutura de diretórios dos arquivos pessoais até a forma de se organizar o desenvolvimento do sistema como um todo.
O desenvolvimento web mudou muito nos últimos tempos. Podendo muitas vezes causar um sentimento de confusão com tamanha gama de possibilidades e diferentes métodos de se alcançar os mesmos resultados.
Nessa linha, pode-se citar os frameworks de desenvolvimento, que “tem como principal objetivo resolver problemas recorrentes com uma abordagem genérica, permitindo ao desenvolvedor focar seus esforços na resolução do problema em si, e não ficar reescrevendo software.” [definição disponível em 25/07/2020 em https://tableless.github.io/iniciantes/manual/js/o-que-framework.html]
Este artigo, como o próprio título já diz, visa apresentar uma sugestão de arquitetura de camadas para ser utilizada no desenvolvimento web utilizando o framework Laravel.
Motivação
Muitas vezes, durante o desenvolvimento de um software, ou mesmo de uma nova funcionalidade, o desenvolvedor precisa decidir quais os locais ideais para depositar sua regra de negócio, seu acesso ao banco de dados, e assim por diante. A arquitetura aqui proposta tem a intenção de deixar essa decisão predefinida e, com isso, acabar com as dúvidas nesse sentido, fazendo com que haja um ganho de produtividade por excluir da equação esse “tempo perdido”. Dessa forma, a palavra chave aqui é padronização gerando assim uma melhor organização.
Introdução
Gerar a estrutura de arquivos, definida nesta padronização que comentei, pode ser um tanto quanto burocrático e repetitivo, uma vez que os arquivos referentes à estrutura em si são muito parecidos. Para resolver este problema, desenvolvi um pacote instalável que trata da burocracia envolvida e, com isso, permite ao desenvolvedor focar somente na funcionalidade que se deseja trabalhar. O nome deste pacote é GzLayers, mas vou deixar para apresentá-lo mais adiante pois neste momento quero focar em explicar o funcionamento da arquitetura aqui proposta.
O funcionamento do sistema que utiliza esta estrutura é, de certa forma, bem simples, uma vez que cada camada tem sua responsabilidade bastante específica. Isso facilita o processo de construir métodos com apenas uma responsabilidade, de tamanho reduzido, e facilita a inclusão de novas funcionalidades permitindo que o reuso seja praticado com maior frequência.
Funcionamento
Estrutura
- Rota aciona Controller
- Controller utiliza Request personalizado para realizar validações
- Controller aciona BO (Business Object) e, como forma de retorno, utiliza Resource
- Resource converte os dados no formato correto de retorno para API
- BO (Business Object) é responsável pela regra de negócio e aciona Repository para acessar/persistir no banco de dados
- Repository se conecta ao Model para executar suas ações
- Model (de maneira geral) faz o mapeamento do banco de dados
Rotas
As rotas devem ser criadas da mesma forma que normalmente são criadas em qualquer outra arquitetura, onde a requisição HTTP recebida acionará um método do controller correspondente.
Controller
Os controllers são desenvolvidos no formato de resource, ou seja, devem conter os seguintes métodos, mas não necessariamente somente eles:
- index()
- store($modelRequest)
- show($modelRequest)
- update($modelRequest, $modelInstance)
- destroy($modelInstance)
Resource
Os arquivos de Resource definem a estrutura da resposta por meio de um método toArray($request) a ser implementado neste tipo de arquivo.
Request
Os arquivos de Request são específicos de cada model, ou seja, se existir um model chamado Pessoa, existirá um request chamado PessoaRequest.
Sua função é gerenciar as validações no backend e, com isso, garantir a consistência dos dados que serão persistidos.
As regras de validação devem ser implementadas em métodos específicos, de acordo com o método que acionou a validação, da seguinte forma:
- store($modelRequest) acionará o método validateToStore()
- update($modelRequest, $modelObject) acionará o método validateToUpdate()
*Obs.: O prefixo validateTo ao nome do método que aciona a validação é obrigatório!
BO (Business Object)
Os arquivos de de BO são os responsáveis por conter toda a regra de negócio.
Após receberem os dados já validados pelo ModelRequest, é nesta camada que os dados são submetidos às regras específicas do sistema antes/depois de acionar o Repository correspondente.
Repository
Os arquivos de Repository são os responsáveis por criar as consultas e persitências ao banco de dados.
É nesta camada que são construídas as queries (das mais simples às mais elaboradas) e, para isso, faz-se acesso ao Model correspondente.
Model
Os arquivos de Model são praticamente os mais simples da estrutura, responsáveis apenas por armazenar as particularidades do recurso específico, mapeando a tabela correspondente no banco de dados a um objeto a ser tratado pela aplicação.
ATENÇÃO!!
Se uma aplicação utiliza ambas as formas de desenvolvimento (API e Views), esta arquitetura prevê que a única coisa a ser trabalhada diferente é o Controller (e sua rota):
- Para cada rota, seu controller específico deverá ser acionado.
- Rota do tipo web (web.php) acionará o controller de web que chamará o BO e retornará uma view
- Rota do tipo API (api.php) acionará o controller de API que chamará o BO e retornará um Resource
- Note que, em ambos os casos, somente a rota e o controller são “duplicados”, pois todo o restante da estrutura é compartilhado
GzLayers
O pacote GzLayers foi criado para facilitar o processo de desenvolvimento, evitando que o desenvolvedor gaste tempo criando arquivos de estrutura. Ao instalar este pacote, diversos comandos serão adicionado à lista de comandos do php artisan, são eles:
- gzlayers:makebo
- gzlayers:makecontroller
- gzlayers:makecrud
- gzlayers:makemodel
- gzlayers:makerepository
- gzlayers:makerequest
- gzlayers:makeresource
- gzlayers:maketrait
Instalação
De dentro do diretório raiz de um projeto laravel, basta instalar via composer da seguinte forma:
composer require cauegonzalez/gzlayers
Uma vez instalado o pacote, os comandos citados estarão disponíveis para serem acionados, possibilitando o envio de algumas parametrizações:
Opção interativa ( — —interactive)
Todos os comandos criados possuem o modo interativo. Basta adicionar o parâmetro --interactive ao final do comando que o modo interativo será iniciado perguntando ao usuário coisas como o nome do Model desejado e confirmando os dados antes da execução propriamente.
Opção “tudo” ( — — all)
Todos os comandos criados possuem o modo “tudo”. Basta adicionar o parâmetro --all ao final do comando que este modo lerá todas as tabelas do banco de dados (exceto as tabelas gerenciadas pelo laravel: ‘migrations’, ‘failed jobs’, ‘password_resets’) e criar o arquivo da estrutura solicitado para cada tabela. Para isso, basta que o projeto já tenha um acesso ao banco de dados configurado e que as tabelas existam no banco indicado pelo arquivo .env.
Opção de não sobrescrita ( — — overwrite=false)
Todos os comandos criados possuem o modo para prevenir a sobrescrita de um arquivo já existente. Basta adicionar o parâmetro --overwrite=false ao final do comando que o arquivo solicitado será criado evitando que o arquivo original seja sobrescrito. Para isso, caso o arquivo solicitado já exista, um novo arquivo é criado, adicionando a data atual ao final do nome.
Obs.: quando utilizada a opção de não sobrescrita deve-se tratar manualmente a comparação entre o arquivo anterior e o novo e então aplicar os trechos de código que se fizerem necessários no arquivo original. É necessário ainda excluir o arquivo novo (aquele que possui a data em seu nome).
Comandos disponíveis
Neste ponto é interessante ressaltar que todos os comandos disponiblizados utilizam o nome do Model como referência, ou seja, o primeiro parâmetro de cada comando é o nome do model desejado.
php artisan gzlayers:makebo
- Este comando gera o arquivo de Business Object (BO) referente ao model informado como parâmetro.
php artisan gzlayers:makecontroller
- Este comando gera o arquivo de Controller referente ao model informado como parâmetro.
php artisan gzlayers:makecrud
- Este comando gera o esqueleto de todas as estruturas referente ao model informado como parâmetro. No modo interativo poderá escolher, além do nome do model, a opção de gerar ou não sobrescrita, o nome da tabela correspondente e se ela possui ou não as colunas de timestamp.
- Este comando aceita também os parâmetros timestamp (true ou false) e o parâmetro table (nome da tabela no banco, caso seja diferente do padrão utilizado no laravel)
php artisan gzlayers:makemodel
- Este comando irá gerar o arquivo de Model informado como parâmetro
- No modo interativo poderá escolher, além do nome do model, o nome da tabela correspondente e se ela possui ou não as colunas de timestamp
- Importante dizer ainda que este comando aceita, além dos parâmetros já citados, os parâmetros timestamp (true ou false) e o parâmetro table (nome da tabela no banco, caso seja diferente do padrão utilizado no laravel)
IMPORTANTE: uma vez que o Eloquent não suporta de forma nativa o uso de chave primária composta, caso seja necessário utilizar este tipo de recurso, o pacote gzlayers indica como dependência o pacote mopo922/laravel-treats que fornece uma trait que faz a “mágica”.
- Basta descomentar a linha 15
// use \LaravelTreats\Model\Traits\HasCompositePrimaryKey;
- E depois criar o atributo $primaryKey da seguinte forma:
protected $primaryKey = [‘key1’, ‘key2’];
php artisan gzlayers:makerepository
- Este comando irá gerar o arquivo de Repository referente ao model informado como parâmetro
php artisan gzlayers:makerequest
- Este comando irá gerar o arquivo de Request referente ao model informado como parâmetro
php artisan gzlayers:makeresource
- Este comando irá gerar o arquivo de Resource referente ao model informado como parâmetro
php artisan gzlayers:maketrait
- Este comando irá gerar o arquivo de Trait referente ao model informado como parâmetro
- Este arquivo é utilizado para preparar os dados para persistência
- Se não existir o arquivo PrepareTrait este será criado durante este comando
- Esta trait é responsável por disponibilizar um método chamado prepare que acionará o método de preparação correto, de acordo com a junção do termo prepare com o nome do método que o está invocando.
- Ex.: se dentro do método store() for invocado o método prepare(), o método realmente invocado será o prepareStore()
Concluindo
Ao utilizar o pacote cauegonzalez/gzlayers toda a parte burocrática da criação da estrutura de camadas fica automatizada.
Próximos passos
Criar o arquivo de rotas e, se não for utilizar como API, criar os controllers específicos para retornar views.
Trabalhar na implementação do que realmente importa
Com a estrutura criada, basta iniciar o desenvolviemnto das regras de negócio e outras particularidades,
Agradecimento
O pacote cauegonzalez/gzlayers foi livremente inspirado no pacote andreacivita/api-crud-generator e deixo aqui meu agradecimento público.
Customizado para que a geração do crud servisse para a estrutura de camadas proposta e para que fosse possível criar somente parte da estrutura, quando houver esta necessidade.
Para contribuir:
https://github.com/cauegonzalez/gzlayers
Para dúvidas ou comentários:
cauegonzalez@gmail.com