[Desenvolvimento] Sendo funcional nível Real Life

Programação funcional é um paradigma já bem conhecido e que nos últimos anos vem ganhando ainda mais força, principalmente depois do aparecimento do React e de algumas grandes empresas mostrando suas arquiteturas com linguagens 100% funcionais.

Os “pilares” desse paradigma já são bem manjados, como: funções puras (uma função que sempre retornará o mesmo resultado, dado os mesmos argumentos, e claro, sem efeitos colaterais), imutabilidade, umas coisinhas mais avançadas como higher-order functions (uma função que recebe uma função como argumento ou também retorna uma função) e currying (que é uma função que recebe uma função e pode ir nessa várias vezes).

Geralmente vemos a galera que trabalha com linguagens multiparadigma seguem apenas essas ideias, mas no mundo funcional podemos ir mais a fundo com o uso de Functors (basicamente uma função pega algum valor dentro de um contexto, aplica uma função, e devolve em um novo contexto), Applicatives (além de envolver um valor dentro de um novo contexto, envolve a função inteira devolvendo um novo contexto) e Monads (simplesmente tira o valor de dentro de um contexto para retorná-lo).

Beleza, mas como que a gente realmente faz um sistema funcionar com isso? Tudo falado até agora faz parte do Functional Core (onde fica toda lógica e manipulação de dados). Na vida real a gente precisa de um jeito de grudar tudo isso e fazer funcionar para que realmente todas essas operações individuais façam sentido e é por isso que vou trazer uma imagem muito conhecida.

Essa imagenzinha linda que geral conhece, é a “Arquitetura limpa” do tio Bob. Mas e aí, o que isso tem a ver com todo esse papo funcional? Bem, é seguindo esse modelo lindo que vamos fazer nosso sistema funcional criar vida para no mundo real!

Para que nosso Functional Core possa de fato fazer algo, a gente precisa ter nosso Imperative Shell que basicamente vai ser o lugar que faz toda a ligação do que temos no nosso Core, é ele que vai passar as informações para serem tratadas e devolver para quem precisa (I/O). Sabendo disso, podemos realizar um de-para na nossa arquitetura, as “Use Cases” são nossas funções puras dentro do nosso Functional Core e os “Adapters” é nosso Imperative Shell. O que acontece é o seguinte, quando entra um Input por alguma porta, esse dado vai pelo nosso Shell jogando-o para o nosso Core realizando toda lógica que precisamos para voltar a subir nas camadas e temos nosso Output.

Com isso temos toda nossa lógica isolada (mínimo de coupling), sendo muuuuito fácil escrever testes unitários para as funções (além do mais elas são puras, quase né), permitindo também fazer testes de integração no nosso Shell. É isso, até!