Formas de organizar o seu código em aplicações Ruby on Rails
Escrito em 24 de Novembro de 2021 por Henrique Panham
Atualizado em 23 de Maio de 2024
O framework Ruby on Rails é conhecido pela rapidez que proporciona na prototipagem. Sua arquitetura segue 3 princípios de design:
- Arquitetura MVC – lógica centralizada nos Models; HTML com código Ruby embutido nas Views; Controllers realizando a comunicação entre Models e Views;
- Convenção sobre configuração;
- “Don’t repeat yourself” (Dry).
Apesar das facilidades trazidas por esses princípios, o desenvolvedor ainda poderá se deparar com problemas que surgem conforme uma aplicação Ruby on Rails cresce. Alguns deles são:
- Fat Model, skinny Controller;
- Difícil de seguir o princípio de responsabilidade única;
- Falta de padronização.
Diante dessas dificuldades, o que deve fazer o desenvolvedor? Criar mais classes? Nessas horas, alguns padrões podem ser muito úteis:
Service objects
Também conhecido como Caso de Uso, um Service object concentra a lógica da aplicação e representa a interação do usuário com ela. Ao olhar para os diferentes Service objects, deve se tornar claro o que a aplicação faz.
Form objects
Valida e verifica todo dado que entra na aplicação, garantindo que está correto. Permite a remoção de strong parameters do Controller, validação dos parâmetros do Model e parsing/coerção dos parâmetros do Controller.
Outras opções
Além dos citados, podemos empregar outros tipos de objetos, como os View objects, Policy objects, Query objects, Decorators, etc.
Levando em conta esses padrões, podemos ainda utilizar frameworks de alto nível para auxiliar nessa padronização de arquitetura. Para exemplificar essa opção, escolhemos o Trailblazer.
Trailblazer
O framework Trailblazer fornece uma arquitetura de alto nível para Ruby on Rails. Foi criado por Nick Sutterer, com o objetivo de adicionar convenções ao modo como agrupamos as abstrações (nossos service objects e form objects).
É de muito simples implementação no projeto: pode ser carregado como uma gem e, com isso, permanece como uma dependência do projeto.
gem "trailblazer" gem "trailblazer-rails" # if you are in rails. |
Estrutura
No Trailblazer, você estrutura seu aplicativo por domínio em componentes reais. Eles são chamados de Concepts (conceitos). Os conceitos são implementados para cada entidade em seu domínio de alto nível.
Esses conceitos ficam em “app/concepts”. Todas as classes e visualizações pertencentes a esse conceito (formas, operações e assim por diante) estão neste diretório.
Definição do Model
No Trailblazer, o Model fica com a responsabilidade de ser um local apenas para persistência, tornando-se um objeto de baixo nível com um conjunto de funções muito simples: recuperar e gravar dados em um banco de dados. Ele também pode conter métodos de consulta e escopos para recuperar objetos.
Definição do Controller
Os Controllers tornam-se endpoints HTTP enxutos. Eles podem lidar com a autenticação, diferenciar entre pedidos e formatos como HTML ou JSON e, em seguida, delegar instantaneamente à respectiva operação (sem lógica de processamento).
Definindo uma padronização
Mesmo que o Rails tenha muitas convenções, ainda se trata de um framework muito aberto. Isso permite que os times se organizem da forma que acharem melhor, mas também pode resultar em confusão: muitas vezes, os desenvolvedores adotam práticas de trabalho conflitantes.
Para evitar essas situações, a empresa deve ter protocolos bem definidos e claramente comunicados.
Atuando nesse problema, o Trailblazer auxilia na definição de alguns padrões. Padrões estes que vão além de nomes de tabelas e rake tasks: trazem orientação para questões de arquitetura e padrões com escopos e casos de uso.
O Rails geralmente tem uma resposta padrão: na dúvida, direciona tudo ao Model porque o foco são Controllers com pouco código. Como convenção própria, o Trailblazer identifica claramente as diferentes camadas de aplicações web e fornece abstrações.
Abstrações do Trailblazer
O framework Trailblazer conta com uma série de abstrações. Elas são de uso opcional, e aconselha-se restringir seu uso às situações de absoluta necessidade. As principais abstrações desse framework são Operations e Contracts:
- CONTRACT (Form Objects) – Valida os dados recebidos.
- POLICY (Policy Objects) – Pode ser utilizada para autorizar a execução de código por usuário.
- OPERATION (Service Objects) – Uma implementação service object com um controle de fluxo funcional.
- VIEW MODEL – Componentiza os códigos da view.
- REPRESENTER – Utilizada para serializar e parsear dados recebidos na API.
- DESERIALIZER - Transformadores para analisar os dados recebidos em estruturas com as quais você pode trabalhar.
Operations
Essa abstração é o coração da arquitetura Trailblazer, sua camada de domínio. Ela orquestra validações, Policies, Models, Callbacks e lógica de negócios através de uma pipeline funcional com tratamento de erros integrado.
Além disso, possui uma resposta padronizada para toda a aplicação.
Esse é um exemplo da estrutura padrão de uma Operation:
1. module Boleto:: Operation 2. class Create < Trailblazer:: Operation.version (2) 3. private 4. step Model(Boleto, :new) 5. step :generate_unique_document_number 6. step :save_boleto 7. failure :boleto_already_exists 8. success :generate_in_provider 9. 10. def save_boleto (options) 11. ... 12. end 13. 14. def generate_unique_document number(options) 15. ... 16. end 17. 18. def generate in provider(options) 19. ... 20. end 21. end |
Cada step funciona como um método que pode repassar informação para os steps posteriores. Ao final de cada step, devemos retornar um booleano.
Nessa parte podemos ver com clareza a parte funcional de uma Operation, pois ela segue o padrão chamado de Railway Oriented Programming.
Enquanto os steps retornarem true, a classe continuará no fluxo de sucesso, chegando até o método success na linha 8 do código. Caso algum step retorne false, o circuito será rompido e o método failure (linha 7) será executado.
Contracts
Através de Contracts são implementadas as validações. É uma abstração para lidar com dados arbitrários ou estados de um objeto, sendo ela própria um objeto independente que, nesse caso, é orquestrado por uma Operation.
Abaixo, temos um exemplo da estrutura padrão de um Contract. Property são os parâmetros esperados e Validates funcionam de modo semelhante à maneira que podemos customizar no ActiveModel.
1. module Boleto::Contract 2. class Create < Reform:: Form 3. property :amount 4. property :document number 5. property :expire_at 6. property :notify_debtor, default: false 7. property :seller 8. property :payer 9. 10. validates :seller, presence: true 11. validates :payer, presence: true 12. validate :maximum_amount 13. 14. private 15. 16. def maximum amount 17. return if amount <= Boleto::MAXIMUM AMOUNT 18. errors.add(: base, 'Retorna mensagem de erro') 19. end 20. end 21. end |
Uma vez que você tenha entendido a estrutura de Contract, já pode chamar essa abstração dentro da Operation:
1. module Boleto:: Operation 2. class Create < Trailblazer:: Operation.version (2) 3. private 4. step Model(Boleto, :new) 5. step Contract::Build(constant: Boleto::Contract::Create) 6. step Contract::Validate() 7. step Contract::Persist() 8. step :generate_unique_document_number 9. failure :boleto_already_exists 10. success :generate in provider |
Entre as linhas 5 e 7 vemos as formas como podemos manipular o objeto de Contract e, a partir de seu resultado, seguir no fluxo de sucesso e criar um objeto na base. A validação também pode falhar. Nesse caso, a classe seguirá no fluxo de falha.
O que sobra no Model após as abstrações?
O que sobra no Controller após as abstrações?
Veredito: Vantagens
- Boa documentação;
- Melhora na organização do código;
- Padronização que auxilia no code review;
- Carregar apenas os módulos necessários.
Veredito: Desvantagens
- A atualização da API do Trailblazer da 1.x para 2.x quebra a API pois houve grandes mudanças e quem já possuía um code base grande na versão 1.x teve dificuldade para atualizar todo o projeto.
- Mais coisas para aprender pois modifica bastante o Rails Way.
- Mais 1 dependência para o seu projeto.
Conclusão
Na hora de escolher entre o Trailblazer e suas alternativas (Dry-rb, Interactor, U-case), lembre-se sempre que devemos escolher de acordo com a real necessidade do projeto ou da necessidade padronização do código fonte da empresa.
Analise os pontos positivos e, principalmente, os negativos de cada abordagem.
Escrito em 24 de Novembro de 2021 por
Henrique Panham
Desenvolvedor backend, atualmente trabalhando com Ruby e diferentes frameworks. Tem conhecimentos práticos em desenvolvimento de aplicações com metodologias ágeis e vivência com as linguagens CSS, HTML e Javascript.