Interactors — organizando regras de negócio complexas no código

Gustavo Kloh
Qulture.Rocks Product Blog
4 min readMay 3, 2021
Photo by Joshua Hoehne on Unsplash

Imagine a seguinte situação: você começa a desenvolver uma aplicação Rails. No início, poucos desenvolvedores, pouca complexidade e regras bem definidas — é possível seguir sem maiores preocupações. O time cresce, a aplicação cresce ganhando novos recursos, mais regras de negócio coexistindo, mais lugares para serem alterados e testados e mais complexidade. Legal, mas onde quero chegar?

Com o crescimento da complexidade do código tendo que dar suporte à várias regras, surge a necessidade de uma melhor organização além do clássico MVC. Como fazer?

https://www.monkeyuser.com/2018/architecture/

Interactors

A gem interactor fornece uma maneira simples de organizar as coisas, e como eles bem definem:

Um interactor é um objeto simples com um único propósito.

Interactors são usados para encapsular sua lógica de negócio. Cada interactor representa uma coisa que a sua aplicação faz.

Ou seja, para cada ação que eu quiser posso fazer um interactor. Esse mesmo interactor pode ser reutilizado em vários lugares que necessitam dessa mesma ação dando segurança de que não teremos efeitos indesejados.

A gem também oferece um meio de chamar vários interactors de forma organizada: são os organizers. Um organizer é um interactor que chama outros interactors. Imagine que na criação de um usuário no seu sistema você precisa que o registro seja salvo no banco, envie um email de boas vindas, e uma notificação para o celular. Cada uma dessas ações é feita num interactor e são chamadas pelo organizer.

Motivação

Alguns motivos nos levaram ao uso dessa gem:

  • com a complexidade crescendo, surgiu a necessidade de garantir que uma determinada ação seria executada de forma uniforme, sem efeitos inesperados como um callback ou implementações diferentes
  • facilidade para entender o fluxo de uma ação
  • facilidade para testar
  • diminuir os callbacks em modelos
  • organizar use cases

Aqui na Qulture nós valorizamos bastante que o produto esteja disponível o mais rápido possível para os clientes, podendo assim iterar sobre ele. Isso quer dizer que nem todos os fluxos estão disponíveis desde o primeiro dia.

Por exemplo: estamos desenvolvendo um fluxo de gestão de cargos com o objetivo de chegar em um produto de sucessão. Temos um fluxo básico para arquivar um cargo: quando o endpoint é chamado, um organizer cuida das ações — Core::JobTitle::Archive. E se um usuário quisesse arquivar 150 cargos de uma vez?

Nós não temos certeza se isso é um caso comum, e quais informações são importantes na hora dessa ação. Mas uma vez que temos o objetivo do produto de sucessão e o fluxo simples está pronto, não faz sentido segurar o lançamento por não permitir arquivar 150 cargos de uma vez.

Lançada a parte inicial da gestão de cargos, com o auxílio do nosso time de Customer Success demandas como a descrita acima chegam em forma de tasks que são realizadas pontualmente até que isso entre no produto.

A pessoa que está fazendo a task poderia montar um script que simplesmente faz um update no banco fazendo job_title.update(archived: true). Mas e se ao arquivar um cargo também for preciso desatrelar as pessoas envolvidas nesse cargo? A chamada pro organizer abstrai essas regras de negócio e garante que elas estejam sempre atualizadas.

Outro ponto legal é que os organizers facilitam muito o entendimento de determinada ação. Então no exemplo anterior o organizer parece quase que uma receita, cada interactor descreve o seu próprio passo: Core::JobTitle::RemoveOccupationFromContracts, Core::JobTitle::AssignArchivedAt, Core::JobTitle::Save.

Além de tudo isso, os interactors ajudam para que as regras de negócio fiquem isoladas dos models ou controllers. A regra de negócio fala que ao arquivar um cargo devo remover as pessoas de seus cargos? Vou testar exatamente isso no organizer, sem se preocupar se o request veio correto, ou se o callback do model está funcionando.

Contras

Nem tudo são flores. Como tudo na vida, existe a parte ruim.

Com a aplicação já rodando e muita coisa implementada temos que conviver com callbacks de modelo e interactors coexistindo. Com isso é necessário cuidado, atenção e mais testes em casos de alteração do código existente.

Outro ponto é que com o uso dos interactors a tendência é que os models fiquem com cada vez menos callbacks então uma ação genérica, como um update em um registro, podem não refletir as regras que deveriam.

O uso dos interactors traz facilidades para organizar sua lógica, mas não dá pra afirmar que é a solução perfeita para todos os casos. O ideal é avaliar cada contexto e chegar na conclusão do melhor caminho. Aqui para nosso contexto, depois de avaliar as soluções, achamos esse um bom caminho.

O resultado é que novos desenvolvedores no time, ou ainda os antigos quando precisam fazer alterações de coisas que não estão tão frescas na memória, conseguem ter um caminho mais rápido para entender, alterar e testar com mais segurança dentro de uma base de código que hoje é naturalmente complexa.

--

--