Padrões de design comuns em Ruby on Rails
Não importa em que nível você esteja, você certamente sabe que Ruby on Rails é um framework MVC. Pelo que eu suponho, você entenda que o Model, View, Controller no qual o Rails é baseado é um Design Pattern muito comum em Desenvolvimento Web. Mesmo muitos outros Frameworks usam este padrão: Laravel, ASP.NET MVC, Django, etc.
Mas, apesar de todos os frameworks usarem essa forma de organizar o código, sua comunidade tem padrões, vamos chamá-los de “filhos” desse padrão, que eles nomeiam e usam de forma diferente. Este artigo aborda esses padrões de design para a comunidade Rails.
Mas primeiro vamos definir: o que é um Padrão de Design?
Padrões de projeto são soluções (testadas) para problemas de projeto concretos (sob um determinado contexto). Eles fornecem algumas vantagens:
- Alta qualidade de software.
- Mais produtividade.
- Melhor comunicação da equipe.
- Melhoria nas habilidades de design em programadores inexperientes.
Padrões de projeto são para projetar o que algoritmos são para programação. Ambos fornecem soluções para problemas concretos.
O Quicksort fornece uma solução para classificação, enquanto o padrão Observer é ideal para enviar atualizações para componentes de software interessados em recebê-los.
Por exemplo, como você projetaria seu código para um aplicativo da Web que possui uma interface de usuário da qual podemos buscar e recuperar informações de um banco de dados?
Primeiro, você separa esta situação em duas partes ou em dois problemas a serem resolvidos. O primeiro problema a resolver é a parte que capta e consulta as informações do banco de dados: o modelo. O segundo problema é definido pela parte do seu software que exibe essas informações: a visualização. E um terceiro problema, que aparecerá mais tarde, é a parte que conecta a view ao model: o controller.
Como você deve ter notado, o que acabamos de descrever é o padrão MVC.
Fat Controller e FatModel
Seu primeiro problema de design Rails
Após suas primeiras milhares de linhas de código no Rails (ou talvez em qualquer outro framework MVC) você notará que à medida que sua funcionalidade cresce, seus controladores ficam cada vez maiores, tanto em número de linhas quanto em número de responsabilidades. Muitas vezes isso gera muito código duplicado para manter e principalmente lógica de negócio definida como Fat controller.
Para exemplificar o acima, usaremos o caso a seguir, seguido de seus respectivos layouts de classe/objeto e código Rails.
Dado um valor de reembolso, uma data e um fólio, devemos reembolsar o valor especificado do valor para um movimento no sistema de cartão de crédito. Ao fazer o reembolso, a equipe de reembolso deve ser notificada. Concluído o acima, o cliente deve ser notificado.
O diagrama a seguir mostra aproximadamente o fluxo (bem como o Fat Controller):
Este código demonstra melhor o acima. Este é o seu primeiro problema. Mas porque?
O problema ocorre quando você começa a ter muita lógica de negócios no controlador. Essa classe não tem mais uma responsabilidade única e conecta a view com o model, então agora ela passa a ser responsável por detalhes como:
- Validar a entrada do usuário.
- Enviar e-mails de boas-vindas.
- Extrair informações da tabela A para conectá-las à Tabela B e armazená-las na tabela C.
Nosso próximo passo natural pode ser mover toda essa lógica do controlador para o modelo. Clique aqui para saber mais.
Agora nosso FatController já é um SkinnyController, mas nosso Model agora é um FatModel, que dilema!
Seu segundo problema
Um Fat Model começa quando você decide adicionar métodos, refatorando seu código em um controlador ou simplesmente porque você decide que é uma boa ideia colocar lógica de negócios no modelo (herdado do ActiveRecord).
Lembremos que ActiveRecord é um padrão usado para definir a estrutura de uma tabela em classes que, por sua vez, são usadas para armazenar e até modificar o conteúdo dessa tabela (ler, adicionar, modificar, excluir).
O problema de adicionar lógica de negócio ao Model (uma classe herdada do ActiveRecord) é que aos poucos você começa a se afastar da função principal da classe: ler, adicionar, modificar, deletar e das próprias adições do Rails como validações, retornos de chamada , escopos etc. Devido ao exposto, seu modelo agora tem muitas responsabilidades. Isso é chamado FatModel.
Objetos de serviço
Uma vez definido o problema, podemos prosseguir com a solução. E este é chamado de Serviço`*`. Coloquei o `*` referente aos três tipos de Services que podemos encontrar em diferentes comunidades Rails: Service Object, Service Class e Application Service. Para mim e apenas para o contexto em que nos encontramos, usarei o termo Objeto de Serviço no restante do artigo para me referir ao padrão que descreveremos a seguir.
Um Objeto de Serviço é responsável por fornecer todas as classes necessárias para executar uma lógica de negócio, processo ou uma definição mais rudimentar: lógica ou procedimento de um script. De tal forma que direcione o fluxo através dos diferentes modelos e operações necessárias para realizar uma ou várias ações.
No Rails, essas classes quase sempre são identificadas como tendo um método público (qualquer instância ou classe chamada chamada para executar). No nosso caso, o método da instância pública será chamado e o objeto deverá ser inicializado com todas as estruturas necessárias para executar tal procedimento.
Na comunidade Ruby on Rails, este tipo de classe é frequentemente conhecido como Service Object, mas em outras comunidades pode ser referido como Service Class, Stategy ou Commands*. Na verdade, algumas pessoas na comunidade Rails usam o nome Commands para outro contexto, como veremos mais adiante.
Diante do exposto, podemos supor que:
InputValidator=InputValidatorService=InputValidatorStrategy= InputValidatorCommand
Como tal, a definição de padrão de projeto está sempre vinculada a uma comunidade que a utiliza e sempre pode haver padrões idênticos, mas com nomes diferentes em comunidades diferentes. Mesmo na mesma comunidade (como Rails) pode haver discussões intermináveis sobre a nomenclatura correta desses tipos de classes.
Usando o caso de uso proposto, o diagrama e o código ficarão assim:
Você pode conhecê-lo melhor clicando aqui.
Objeto de serviço
Você provavelmente já identificou o padrão de falha em tudo isso e é exatamente onde eu queria que chegássemos. Tudo o que estamos fazendo é passar a lógica gorda de uma classe para outra. Muitas vezes essa refatoração é válida e lhe servirá bem. No entanto, o problema surge quando você tem muitos objetos de serviço que fazem a mesma coisa ou simplesmente seguem um fluxo de procedimento. Basicamente, acabamos (em linguagens dinâmicas) com um grande script dentro de uma classe.
Portanto, tentarei definir as seguintes heurísticas para evitar que seus Objetos de Serviço se tornem grandes.
Heurísticas não são ruins
Todos nós usamos heurísticas. Não há tempo para reinventar e não há tempo para re-explorar um problema que até nós mesmos já resolvemos antes. Na comunidade Ruby on Rails, o lema é convenção sobre configuração. Em termos coloquiais: se a comunidade já resolveu um problema de design, vamos reutilizar sua solução e implementação. Foi exatamente isso que tornou o Rails ótimo por tanto tempo.
Portanto, vou apresentar mais dois padrões para tornar seu design um pouco melhor, bem como algumas heurísticas para ajudá-lo a identificar quando usá-lo.
Comandos
Tiramos isso da comunidade DDD, principalmente do livro Domain-Driven Rails de Robert Pankowecki & Arkency Team. Refere-se a Comandos como classes que realizam validação simples **de estruturas que não são modelos** e não requerem nenhuma lógica de negócios.
Os comandos serão objetos que receberão nosso objeto de serviço como argumento de inicialização. Recomenda-se usar apenas um comando por objeto de serviço. No entanto, a experiência lhe ensinará qual número de itens funciona melhor para você.
Heurísticas: use-as para definir as classes que recebem seus Objetos de Serviço. Eles podem ser: Plain Old Ruby Objects ou você pode usar alguma gem: dry-rb, virtus, etc.
Objetos de domínio
Eles são objetos que fornecem uma maneira orientada a objetos de lidar com lógica complicada. Em vez de usar um Objeto de Serviço que é responsável por realizar todo um procedimento, os Objetos de Serviço delegam responsabilidades de lógica de negócios aos Objetos de Domínio.
Heurísticas: use-as quando um Objeto de Serviço tiver muitas responsabilidades. Lembre-se de modelar bem esses tipos de classes, que muitas vezes acabam sendo Plain Old Ruby Objects.
Dicas finais
Lembre-se: sua habilidade mais importante é projetar software. Neste post falamos sobre algumas heurísticas e finalizamos com os Objetos de Domínio. Se você olhar de um ponto de vista fora do Rails (ou qualquer outro framework), essas são apenas classes bem modeladas para atacar problemas dentro do domínio do negócio.
Espero ter ajudado você a colocar sua aplicação Rails.