Behaviours em Elixir
Tags: elixir, erlang, tutorial
Published: 2021-06-01 01:42:39

Assim como `Protocols`, `Behaviours` também são uma forma de manter um padrão de *interface/herança* e até mesmo *polimorfismo* no **Elixir**.

Vale lembrar que Elixir é uma linguagem de programação funcional, portanto, não é o nosso forte o conceito de objeto, classe e métodos. No final, sempre estaremos falando sobre **módulos** e **funções**!

Então de que me serve possuir uma *abstração* para criar interfaces em uma tecnologia como esta?

# Cenário
Imaginem que vamos desenvolver uma aplicação onde a pessoa insere seus dados para consultar todos seus cartões de crédito, verificar se as faturas estão em dia, e inclusive gerar possíveis estratégias financeiras, etc...

Para isso precisamos criar uma *API REST* que irá consultar de diversas fontes de cartões para validar os dados e processar as respostas finais.

Você consegue identificar um padrão nisso? Se precisamos consultar mais de uma API externa para reunir os dados para a resposta final, isso significa que nosso código terá muitos módulos que serão correspondentes a provedores diferentes (ex: Itaú, Nubank, etc...)

# Problema
Como iremos mapear todos esses módulos de provedores que vamos criar dentro do nosso sistema então? Como garantir que eles precisam ter uma execução semelhante, retornando os dados processados da mesma forma? 

Tendo em vista que as APIs externas podem ser distintas, possuir dados diferentes e respostas diferentes, isso pode se tornar um problema e pode nos levar a criar códigos aleatórios para solucionar cada tipo de API de uma forma desnecessária.

# Behaviours
Com **behaviours** conseguimos criar um padrão comportamental, definindo uma assinatura pré-exigida pelo módulo, garantindo que todos os módulos subsequentes que irão se especializar naquela API precisarão implementar aqueles requisitos.

Em outras palavras, considere sendo essa a nossa interface de acesso à APIs externas:
```elixir
defmodule Bank.API do
  @moduledoc """
  Este módulo é responsável por definir uma interface 
  padrão para os demais módulos de acesso ao 
  Banco implementarem.
  """

  @type params() :: map()
  @type response() :: {:ok, map()} | {:error, String.t()}

  @doc """
  Este método irá acessar a base externa e
  retornar os dados bancários da pessoa usuária
  """
  @callback call(params()) :: response()
end
```

Ignorando os *moduledocs* e *typespecs* do nosso módulo, nos resta essa `@callback`, que é exatamente o comando utilizado para definir as funções que deverão ser implementadas pelo módulo que pretende se especializar nessa API.

Perceba o quão genérico nossa interface ficou, ela só da a entender que é uma API de Banco, com uma função de chamada. Em momento algum foi especificado qual o tipo de banco, ou como essa chamada será feita (via: API, gRPC, SOAP, etc...), isso será total responsabilidade de quem for implementar essa interface.

Agora com nosso **behaviour** bem definido, podemos seguir para a implementação do primeiro Client que iremos utilizar em nossa aplicação para consultar os dados:
```elixir
defmodule Bank.Nubank do
  @moduledoc """
  Módulo que implementa a requisição para a API do Nubank.
  """

  @behaviour Bank.API

  @impl true
  def call(%{user_id: id}) do
    id
    |> nubank_url()
    |> HTTPoison.get()
    |> case do
      {:ok, %{status_code: 200, body: response}} ->
        {:ok, response}

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp nubank_url(id), do:
      Application.get_env(:my_app, :nubank)[:url] <> "/user/#{id}"
end
```
*(a requisição acima é meramente ilustrativa 😅)*

O comando `@behaviour Bank.API` descreve que nosso módulo `Nubank` irá se especializar no `Bank.API`, logo, ele deverá implementar a seguinte função: `call/1`.

Com isto, temos nosso primeiro provedor implementado, yaay 🚀!

# Conclusão
Gosto de dizer que estamos definindo padrões comportamentais, com isso, quando vemos que um módulo específico implementa um **Behaviour**, já dá para ter uma ideia de quais funções ele roda, qual o seu propósito, como ele deve ser implementado em termos de *input* e *output*.

Esse tipo de padronização ajuda a escalar nosso código, diminuir a curva de aprendizagem para novas pessoas desenvolvedoras e inclusive é uma ótima ferramenta para auto-documentar nosso código, deixa tudo mais descritivo e explícito!

E você, o que acha de **behaviours**? Gostaria de complementar, adicionar ou remover alguma informações deste tópico? 

![that's all](https://media.giphy.com/media/l4pTjOu0NsrLApt0Q/giphy.gif)