Dependências: Bibliotecas ou OTP Applications?
Tags: elixir, otp, xp, scrumchkin
Published: 2020-06-20 21:03:18

## O projeto: Scrumchkin Online

Há cerca de um ano atrás criei um jogo de cartas para ensinar Scrum: [o Scrumchkin](http://scrumchkin.com). O jogo tornou o processo de aprendizado mais divertido e foi adotado por _Scrum Trainers_ de diversos países, até que a pandemia inviabilizou qualquer turma presencial.

E foi daí que surgiu meu projeto pessoal: criar uma versão online do Scrumchkin. O que seria uma ótima oportunidade para brincar e aprender mais sobre *Phoenix Liveview*.

Inicialmente, pensei na seguinte estrutura para o projeto:

![Estrutura do Projeto](https://dev-to-uploads.s3.amazonaws.com/i/abwbd55vbctxeu2r7jy5.png)

Desta forma, seria possível criar jogos em processos separados e ter um registro com identificadores únicos de cada jogo para que cada partida pudesse ser acessada através de uma URL diferente.

Exemplo:

* O usuário acessa a URL `http://scrumchkin.com/game/abc123`
* A aplicação web pergunta ao Registro de Jogos onde está o jogo `abc123`
* O Registro de Jogos encontra o PID da partida e retorna para aplicação web

### O Registro de Jogos como uma biblioteca

Tendo em mente o **princípio da responsabilidade única**, o desenho acima deixa bem evidente a existência de 3 projetos diferentes: O Registro de Jogos, o Servidor de Jogos e a Interface Web.

_Os próximos parágrafos vão falar sobre alguns aspectos técnicos de Elixir a título de curiosidade. Se você quiser apenas entender a diferença entre uma biblioteca e uma aplicação OTP basta pular esta parte_ :)

Tecnicamente o Registro de Jogos é extremamente simples: ele vincula um ID único a uma partida. Ele é basicamente um dicionário que tem como chave um _UUID_ e como valor um _PID_ de um GenServer para uma partida.

Inicialmente, criei o Registro de Jogos como uma biblioteca capaz de fazer operações CRUD em uma _tabela ets_:

```elixir
defmodule GameRegister do
  def init() do
    :ets.new(:scrumchkin, [:set, :public, :named_table])
  end

  def save(value) do
    key = UUID.uuid1()
    :ets.insert_new(:scrumchkin, {key, value})
    key
  end

  def delete(key) do
    :ets.delete(:scrumchkin, key)
  end

  def get(key) do
    :scrumchkin
    |> :ets.lookup(key)
    |> format_result
  end

  def list_all do
    :ets.tab2list(:scrumchkin)
  end

  defp format_result([]), do: {:error, "Game not found"}

  defp format_result(item_list) do
    item_list
    |> hd
  end
end
```

**TL;DR** - A biblioteca armazena o estado atual de partidas e as vincula a um código identificador. Ela é capaz de listar, obter, salvar e deletar partidas do registro.

### Um pequeno problema

Para que eu pudesse utilizar a tabela ets, ela precisava existir. Isto significa que em algum momento a função `init` do código acima precisaria ser chamada pela minha aplicação web.

```elixir
  def init() do
    :ets.new(:scrumchkin, [:set, :public, :named_table])
  end
```

Mas isso vai contra o princípio de responsabilidade única que utilizei para dividir este projeto em partes menores, certo? 


### O Registro como uma aplicação

Mas o que é uma dependência como biblioteca? Ela é uma engrenagem que faz parte de um todo; algo bem parecido com uma peça de Lego. Sabemos onde estão os pinos e buracos e a utilizamos para construir algo maior.

![Dependências como Bibliotecas](https://dev-to-uploads.s3.amazonaws.com/i/sreilk89om7jrmjd1utg.png)

A dependência de uma aplicação OTP é um pouco diferente. 

Pense em um carro. Geralmente, carros têm um mecanismo de refrigeração do motor que é iniciado no momento em que você vira a chave e dá a partida. O carro depende deste mecanismo para funcionar, mas ele é um tanto quanto independente: muitas vezes ele é acionado quando desligamos o carro (aquele barulho de ventilador que vem de debaixo do capô, principalmente em dias quentes).

Esse mecanismo de refrigeração tem interfaces com o motor do carro, mas controla seu próprio estado. Existe uma relação clara de **dependência**, mas não de controle. O motor depende do sistema de refrigeração para não superaquecer, mas não o controla.

E o mesmo precisava acontecer com meu Registro de Jogos, que ficou assim:

```elixir
defmodule GameRegister do
  use GenServer

  def start_link(state) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def init(stack) do
    :ets.new(:scrumchkin, [:set, :public, :named_table])
    IO.puts("Tabela scrumchkin criada")
    {:ok, stack}
  end

  def handle_call({:save, game}, _from, state) do
    key = UUID.uuid1()
    :ets.insert_new(:scrumchkin, {key, game})
    {:reply, key, state}
  end

  def handle_call({:delete, game_id}, _from, state) do
    :ets.delete(:scrumchkin, game_id)
    {:reply, :ok, state}
  end

  def handle_call({:get, game_id}, _from, state) do
    result =
      :scrumchkin
      |> :ets.lookup(game_id)
      |> format_result

    {:reply, result, state}
  end

  def handle_call(:list_all, _from, state) do
    {:reply, :ets.tab2list(:scrumchkin), state}
  end

  def save(game) do
    GenServer.call(__MODULE__, {:save, game})
  end

  def delete(game_id) do
    GenServer.call(__MODULE__, {:delete, game_id})
  end

  def get(game_id) do
    GenServer.call(__MODULE__, {:get, game_id})
  end

  def list_all do
    GenServer.call(__MODULE__, :list_all)
  end

  defp format_result([]), do: {:error, "Game not found"}

  defp format_result(item_list) do
    item_list
    |> hd
  end
end
```

### Mas... o que muda?

Minha aplicação web não é responsável por criar a tabela ets. Ela apenas diz que depende do Registro de Jogos e que ele é agora uma __aplicação extra__. 

A alteração no arquivo `mix.exs` é simples:

```elixir
  def application do
    [
      mod: {Scrumchkin.Application, []},
      extra_applications: [:logger, :runtime_tools, :game_register, :game_engine]
    ]
  end
  defp deps do
    [
      {:game_engine, path: "../game_engine"},
      {:game_register, path: "../game_register"}
    ]
  end
```

Agora, toda vez que inicio minha aplicação com um `mix phx.server` omeu registro de jogos é iniciado automaticamente e assume a responsabilidade de criar a tabela ets onde vai armazenar os PIDs das partidas de Scrumchkin.

A minha aplicação web **depende** do Registro de Jogos, mas confia que ele consegue resolver seus problemas sozinho.