Behaviours em Elixir

wlsf

Willian Frantz

Posted on June 1, 2021

Behaviours em Elixir

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

(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

💖 💪 🙅 🚩
wlsf
Willian Frantz

Posted on June 1, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Using the Keyword module for options
elixir Using the Keyword module for options

December 26, 2023

Behaviours em Elixir
elixir Behaviours em Elixir

June 1, 2021

Processos em Elixir
elixir Processos em Elixir

November 28, 2020