Back-End

20 mai, 2015

A maldição da linguagem e o Elixir

Publicidade

É comum ouvirmos que todo software tem bugs e falhas. Parece que todos os sistemas possuem alguma maldição ancestral que nos faz sofrer com isso. Nossas defesas são quase sempre ineficientes: mesmo que tratemos de todas as exceções sintáticas, outras que estão para além da nossa alçada ainda aparecem. Será que essa maldição não tem nenhuma tolerância?

Para desenvolver sistemas de telecomunicação tolerantes a falhas, um grupo de pesquisa da Ericsson estudou por volta de 20 linguagens de programação a fim de encontrar qual seria a melhor plataforma para seus requisitos. Isso ocorreu na primeira metade da década de 80, e o veredito foi que nenhuma linguagem possuía tudo o que eles precisavam. Criaram, portanto, o Erlang, para codificar sistemas que possuem nove noves de disponibilidade (ou 99,9999999% de uptime).

Será que eles não eram suficientemente versados nessas quase 20 linguagens? Será que um try/catch global não resolveria? Aparentemente não. Tudo indica que eles estavam certos em criar uma plataforma nova, pois ela influenciou diversas outras linguagens ditas orientadas à concorrência pelo modelo de ator e mensagem. O que eles perceberam é que o menor sistema tolerante a falhas é um sistema que executa em duas máquinas diferentes (e não simplesmente um try/catch global). Por mais que cuidemos de tudo aquilo que é executado em nosso sistema, não podemos garantir que um disco rígido não se corrompa, por exemplo. Então, para mitigar as possibilidades de falhas, precisamos de um sistema distribuído, paralelo e concorrente.

O sucesso da plataforma é muito grande dentro da Ericsson, porém nem tão grande fora da empresa. Apesar de ser um projeto open source desde 1997, o Erlang não possui uma das maiores taxas de adoção da indústria. Quem normalmente leva a culpa por esse fato é a sintaxe da linguagem. Ela é, no mínimo, um tanto quanto diferente do que as pessoas estão acostumadas. Apesar de ser uma linguagem muito sucinta (poucas palavras reservadas, uma biblioteca padrão pequena, porém completa, etc), sua sintaxe foge dos padrões “C”. Quer ver um exemplo?

-module(ola_mundo).
-compile(export_all).
diz_ola(Nome) →
io:print(“Olá ~p~n”, [Nome]).

(Será essa sintaxe tão estranha assim????)

Enfim, há outros que dizem que o problema é a linguagem ser funcional, outros que reclamam do suporte a testes, outros da nomenclatura utilizada, outros da falta de tooling etc. Fato é que muitos encontram problemas para se iniciar neste mundo concorrente, paralelo e distribuído.

Enquanto de um lado o problema de distribuição estava resolvido (e por conseguinte a tolerância a falhas), de outro a facilidade de uso era o que estava em alta. José Valim, um core commiter do Rails, tinha muita facilidade para trabalhar com Ruby. O ferramental é uma prioridade na plataforma, possui uma sintaxe amigável (apesar de não ser como “C”) e uma grande comunidade. No entanto, quando foi incumbido de melhorar a performance multi-threaded da plataforma, ele percebeu que se virar com threads, locks, mutexes e outros construtos concorrentes não é nada fácil. Assim ele também foi procurar uma plataforma que lhe facilitasse a vida.

Talvez não tenha sido 20 linguagens que tenha visto, mas ele também chegou à mesma conclusão: criaria uma nova linguagem que executaria na máquina virtual do Erlang. Ele queria manter a facilidade de desenvolvimento que tinha com Ruby e Rails mas em uma plataforma nativamente distribuída, concorrente e paralela. Daí surgiu o Elixir!

Revisitando o exemplo, agora em Elixir:

defmodule OlaMundo do
            def diz_ola(nome) do
                        IO.puts “Olá #{nome}”
            end
end

A estrutura é simples e muito familiar para quem já lê código Ruby. Não há classes e objetos, mas certamente ele criou uma ponte principalmente para a comunidade de rubistas. Ficou mais fácil obter as vantagens da plataforma Erlang.

Claro que, como todo bom artista, ele não mudou apenas a sintaxe da plataforma, mas também incluiu alguns ingredientes próprios que ele sentia falta em Erlang puro: metaprogramação, polimorfismo (por meio de protocols), macros para a extensão da linguagem (influenciado por macros em Lisp) e uma estrutura padrão de projetos com todo o ferramental incluso (testes, configuração de ambientes, definição de dependências e etc).

Com tudo isso, talvez cada um de nós consiga criar o próximo servidor que irá aguentar 2 milhões de usuários simultâneos em apenas um “box” (máquina), como é o caso do WhatsApp. Talvez este número pareça demagogia, mas isso é possível por meio do modelo de concorrência da plataforma. Ao invés de nos basearmos em threads de execução nativas, a VM do Erlang se baseia em processos leves que ela mesma gerencia. Estes processos são extremamente leves pois não compartilham nenhum estado. Isso assusta quem está acostumado a compartilhar estado usando objetos (e os acessando de diferentes threads), mas na verdade é muito simples: cada processo possui uma caixa de mensagens (como um e-mail) e só se comunica com outros processos enviando mensagens. Então, ao invés de criarmos uma instância de um objeto Thread e iniciá-lo, fazemos o seguinte:

# 1 - guardamos uma referencia ao processo atual
parent = self()
 # 2 - criamos um processo com spawn_link
 spawn_link(
 fn -> send parent, {:msg, "hello world"}
 end)
# 3 - aguardamos receber a resposta e imprimimos no console
 receive do
 {:msg, contents} -> IO.puts contents
 end

Explicando em detalhes (podemos executar estas linhas no próprio shell do Elixir que iniciamos com o comando iex):

  1. Pegamos uma referência ao processo atual (se executado no shell teremos o id do processo do shell);
  2. Criamos um processo que executará uma função;
  3. Definimos a função que será executada no processo como uma função que envia uma mensagem para o processo que tem o id guardado em parent (aquele que guardamos no primeiro passo);
  4. Fazemos o processo bloquear até que chegue uma mensagem (que deverá acontecer instantaneamente já que enviamos a mensagem no passo 3).

Parece difícil? Fizemos um exemplo com pouquíssimas linhas que distribui um processamento. Podemos evoluir o exemplo para que cada processo esteja executando em nós de Elixir diferentes. A plataforma foi pensada com essa distribuição desde o início e a sintaxe agora não é mais desculpa! Nem o ferramental, nem a comunidade, nem a disponibilidade de bibliotecas e etc., etc., etc. Bem-vindo ao mundo dos alquimistas!

Veja também:

***

Texto publicado originalmente na Revista iMasters.