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 24 de Agosto de 2023

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.

 

Posts relacionados

homem segurando um celular e um cartão de crédito fazendo um pagamento online
mão de mulher segurando celular com tela aberto do Drex