1 pontos por GN⁺ 2025-04-13 | 1 comentários | Compartilhar no WhatsApp
  • O ponto central que permitiu ao Erlang criar sistemas distribuídos confiáveis não está nos processos leves em si, mas nos behaviours, que generalizam padrões recorrentes de concorrência e tratamento de falhas
  • Os behaviours fornecem pontos de implementação como uma interface, mas ao mesmo tempo escondem o tratamento de concorrência dentro de componentes comuns, como gen_server, fazendo o código da aplicação se aproximar de uma lógica sequencial
  • A tese de Joe Armstrong trata de um caso em que sistemas grandes como o AXD301 foram compostos usando apenas pequenos blocos como gen_server, gen_event, gen_fsm, supervisor, application e release
  • O supervisor reinicia processos que falharam com estratégias como one_for_one e one_for_all, e a filosofia “Let it crash!” transfere a responsabilidade de recuperação para a árvore de supervisors
  • Behaviours em forma de máquina de estados facilitam testes por simulação e verificação formal, permitindo que desenvolvedores foquem na semântica do problema em vez da complexidade da concorrência e do simulador

O problema que Erlang tentou resolver

  • Erlang começou como uma ferramenta para criar sistemas distribuídos confiáveis
    • No início, era uma biblioteca Prolog para criar sistemas distribuídos confiáveis; depois passou por um dialeto de Prolog até se tornar uma linguagem independente
    • Foi usado na programação das centrais telefônicas da Ericsson e, nas décadas de 80 e 90, lidou com tráfego na escala de centenas de milhões de pessoas e SLAs rigorosos
  • A Ericsson proibiu o uso de Erlang em 1998, e a equipe de desenvolvimento argumentou que, se fosse para proibir, seria melhor liberar como open source; a Ericsson acabou fazendo isso
  • Joe Armstrong foi uma das figuras centrais no design e na implementação de Erlang e, em 2002, começou sua tese de doutorado no SICS, concluindo em 2003 Making reliable distributed systems in the presence of software errors
  • Essa tese foca menos em matemática ou teoria e mais nas ideias por trás do Erlang e na experiência de construir sistemas distribuídos confiáveis
  • A grande ideia do Erlang não é processos leves e passagem de mensagens, mas os componentes genéricos chamados de behaviours no Erlang

behaviours: união entre interface e infraestrutura

  • Os behaviours do Erlang são conjuntos de assinaturas de tipos que podem ter várias implementações, como interfaces em Java ou Go
  • Se o programador fornece a implementação dessa interface, pode usar funções genéricas escritas para essa interface
  • A diferença principal é que behaviours vão além de uma interface simples e fornecem também código de infraestrutura
    • O programador da aplicação escreve a semântica do problema, isto é, a lógica de negócio
    • Código de infraestrutura, como concorrência, é fornecido automaticamente pelo behaviour
    • Behaviours incorporam boas práticas escritas por especialistas experientes
    • Se o sistema inteiro reutiliza um pequeno conjunto de behaviours, melhorias na implementação desses behaviours podem melhorar o sistema sem mudanças no código
    • O uso de behaviours impõe uma estrutura que facilita testes e verificação formal

gen_server: concorrência escondida atrás de código sequencial

  • O exemplo de gen_server é um armazenamento chave-valor: store salva um par chave-valor e lookup busca o valor de uma chave
  • O callback principal, handle_call, atualiza o estado Dict em requisições store e consulta a chave no estado em requisições lookup
  • Ao fornecer essa implementação ao gen_server, cria-se um servidor capaz de lidar com requisições simultâneas de store e lookup
  • O ponto importante é que handle_call em si é totalmente sequencial
    • A concorrência fica escondida dentro do componente genérico gen_server
    • O código da aplicação fica mais próximo de uma forma que recebe estado e entrada e produz novo estado e saída
  • A tese de Joe Armstrong estende essa abordagem para gen_server, gen_event, gen_fsm, supervisor, application e release

O caso AXD301 e a escala dos behaviours

  • Joe Armstrong usa a central telefônica AXD301 da Ericsson como estudo de caso
  • O projeto AXD301 incluía os seguintes componentes
    • 122 instâncias de gen_server
    • 36 instâncias de gen_event
    • 10 instâncias de gen_fsm
    • 20 supervisors
    • 6 applications
    • Tudo empacotado em um único release
  • O AXD301 é um sistema com mais de 1 milhão de linhas de código Erlang
  • O fato de um sistema desse porte ter sido composto a partir de um pequeno conjunto de behaviours é uma evidência central da estrutura do Erlang

O papel de outros behaviours

  • gen_event é um gerenciador de eventos genérico
    • Ele permite registrar handlers de eventos, e o gerenciador executa esses handlers quando recebe mensagens relevantes
    • Joe Armstrong cita usos como logging de erros
    • Um logger de exemplo pode registrar e reportar as cinco mensagens de erro mais recentes
  • gen_fsm, depois renomeado para gen_statem, é um behaviour de máquina de estados
    • Ele é mais adequado para implementação de protocolos
    • Protocolos frequentemente são especificados como máquinas de estados
    • Considera-se que qualquer gen_server pode ser implementado como gen_statem, e vice-versa
  • application é composto pela árvore de supervisors e pelos demais elementos necessários para entregar a aplicação
  • release empacota uma ou mais applications
    • Também inclui código para lidar com upgrades
    • Se um upgrade falhar, deve ser possível fazer rollback para o estado estável anterior

supervisor e “Let it crash!”

  • supervisor é um processo que verifica se outros processos estão funcionando corretamente
  • Se um processo monitorado falha, o supervisor pode reiniciá-lo conforme uma estratégia predefinida
  • A estratégia de exemplo {one_for_one, 5, 1000} significa o seguinte
    • Se packet_assembler, kv ou simple_logger falhar, apenas o processo que falhou é reiniciado
    • Se for necessário reiniciar mais de 5 vezes em 1000 segundos, o próprio supervisor falha
  • permanent, 500, worker significa que esse processo é um worker que deve estar sempre vivo, e que o supervisor concede 500 ms para encerramento normal ao tentar reiniciá-lo
  • Na estratégia one_for_all, se um processo falha, todos os processos filhos são reiniciados
  • Um supervisor pode monitorar outros supervisors e não precisa necessariamente rodar no mesmo computador
  • Essa estrutura opera no nível de threads/processos leves, não no nível de contêineres Docker, então não dá para tratá-la simplesmente como “só Kubernetes”
  • “Let it crash!” assume que um processo que falha será reiniciado
    • O programa expressa apenas o caminho normal
    • Se surgir um problema no caminho normal, ele não tenta se recuperar diretamente e sim dá crash
    • Outro programa acima na árvore de supervisors assume o tratamento
  • Não houve coleta sistemática de evidência da estabilidade de longo prazo do AXD301, mas a tese de Joe Armstrong registra que um grande cliente operava um sistema de 11 nós com 99,9999999% de confiabilidade, segundo um número mostrado em PowerPoint
    • Não foi documentado como esse número foi obtido
    • Outros números de relatórios de downtime também vêm acompanhados da observação de que o método de cálculo não está claro

Se fosse implementar behaviours em outras linguagens

  • Apenas processos leves e passagem de mensagens não bastam para tornar Erlang adequado para sistemas confiáveis
  • Uma explicação mais honesta é a estrutura fornecida pelos behaviours e como essa estrutura leva a software confiável
  • A assinatura de interface de gen_server pode ser vista na forma
    • Input -> State -> (State, Output)
    • Ou seja, recebe uma entrada e o estado atual e produz um novo estado e uma saída
  • Uma forma de transformar essa assinatura sequencial em tratamento de requisições concorrentes seria a seguinte
    • Um servidor HTTP converte a requisição em Input e a coloca em uma fila
    • Um event loop remove a entrada da fila e a entrega à implementação sequencial
    • A saída é escrita de volta como resposta ao cliente
    • Para suportar vários gen_server, cada servidor pode receber um nome, e a requisição pode incluir nome e entrada juntos
  • gen_event pode ser implementado permitindo registrar callbacks para certos tipos de evento em uma fila
  • supervisor pode ser simplificado envolvendo a chamada da função gen_server com um tratador de exceções e notificando o supervisor quando ocorrer uma exceção
    • Se o supervisor não estiver rodando na mesma máquina, fica mais complexo
  • application e release são importantes porque configuração, deploy e upgrade são problemas difíceis, mas não são tratados em profundidade aqui

behaviours e testabilidade

  • A estrutura dos behaviours do Erlang leva a testes por simulação e verificação formal
  • Um interesse mais recente está nos testes por simulação de sistemas distribuídos ao estilo FoundationDB
  • Em testes por simulação, o sistema roda em um mundo simulado, e a simulação controla totalmente o momento de envio das mensagens de rede
  • O FoundationDB criou, para testes por simulação, um dialeto de C++ com actors ou uma linguagem de programação própria
  • Considera-se que essa abordagem conseguiu ir bastante longe usando apenas o seguinte tipo de máquina de estados
    • Input -> State -> (State, [Output])
    • [Output] é uma sequência de saídas
  • O simulador gerencia mensagens com uma fila de prioridade ordenada por horário de chegada
    • Remove uma mensagem da fila
    • Avança o relógio para o horário de chegada dessa mensagem
    • Entrega a mensagem à máquina de estados receptora
    • Gera novos horários de chegada para as mensagens de saída e as reinsere na fila
  • Se tudo for determinístico e os horários de chegada forem gerados a partir de uma seed, é possível explorar vários interleavings e obter falhas reproduzíveis
  • Essa abordagem é mais rápida que Jepsen porque as mensagens são processadas na memória e o relógio avança diretamente para o horário de chegada, sem esperar por timeouts
  • Essa forma de máquina de estados é chamada de “network normal form” e leva à hipótese de que todo programa que recebe e envia pela rede poderia ser refatorado para esse formato
  • O fato de gen_server e gen_statem terem essencialmente a mesma estrutura de tipos pode ser visto como um sinal de que essa estrutura não é arbitrária

Verificação formal e estrutura de máquina de estados

  • Joe Armstrong já disse em uma palestra que é difícil implementar corretamente eleição de líder distribuída
  • Com um simulador, esse problema pode ficar muito mais simples
    • Assim como um túnel de vento ajuda a fabricar aviões, seria possível testar condições extremas, como redes instáveis ou perda de energia, antes de ir para produção
  • Esse simulador pode ser genérico ou parametrizado para behaviours
    • O desenvolvedor não precisa escrever o simulador separadamente
    • A complexidade fica escondida atrás do behaviour, assim como o código de concorrência em gen_server
  • Sobre os testes do FoundationDB, Kyle “aphyr” Kingsbury publicou um tweet sugerindo que os testes do FoundationDB parecem muito mais rigorosos que seus próprios testes com Jepsen
  • A verificação formal também fica mais fácil quando o programa é escrito como máquina de estados
    • O model checking em TLA+ de Lamport assume que a especificação é uma máquina de estados
    • Kleppmann mostrou como lidar com o problema de explosão de estados com indução estrutural usando a estrutura de máquina de estados
  • A estrutura obtida com os behaviours do Erlang pode ser reutilizada para tornar mais fáceis problemas que Joe Armstrong considerava difíceis

Trabalhos relacionados e materiais de referência

  • Trabalhos relacionados que trazem ideias do Erlang incluem
    • Trabalho de Martin Thompson com LMAX Disruptor e aeron para criar event loops rápidos onde behaviours possam rodar
    • Trabalho que adiciona async I/O ao tipo de máquina de estados
    • Trabalho que implementa supervisors com mais detalhes
    • Troca a quente de código de máquinas de estados
  • Como referências, são indicados a tese de doutorado de Joe Armstrong, os OTP design principles e a documentação de gen_server, gen_event, gen_statem, supervisor, application e release
  • Sobre a relação entre o modelo de actors e Erlang, resume-se que os desenvolvedores do Erlang criaram a linguagem sem conhecer o actor model, e que um artigo de Carl Hewitt documenta as diferenças entre processos de Erlang e o actor model
  • O Akka tem “actors” e árvores de supervisors, mas aparentemente não possui equivalentes para os outros behaviours do Erlang, e o conceito de “behavior” do Akka também é diferente dos behaviours do Erlang

1 comentários

 
GN⁺ 2025-04-13
Comentários no Hacker News
  • O impressionante em Erlang e na BEAM é a profundidade dos recursos. No post original, Behaviour/Interface foi a principal impressão, mas para mim o ponto maior é que, ao criar sistemas complexos, dá para fazer com muito menos recursos de desenvolvimento do que em outras linguagens
    O próprio OTP já traz muita coisa. Já trabalhei para compilar Elixir para rodar em dispositivos iOS e, além do processo de release do Erlang, também foi possível usar a biblioteca ei para compilar um Node em C e se comunicar com outros nós Erlang — Erlang, Elixir, Gleam etc. — como em uma rede distribuída comum
    Também foi possível chamar funções de uma aplicação Elixir a partir de C por meio da biblioteca rpc do Erlang. Há overhead de codificação/decodificação, e FFI seria mais rápido, mas cabia tranquilamente no orçamento de latência, e consegui colocar de pé em poucos dias um recurso do qual eu nunca tinha ouvido falar antes
    O ponto maior é que Erlang já resolveu, décadas atrás, muitos problemas com os quais stacks modernas ainda brigam, tanto em escala quanto em custo de implementação. O HN tem um certo carinho clickbait por Erlang/Elixir, mas isso não se traduziu em adoção real, e há empresas queimando dinheiro para implementar coisas que, no stack Erlang, você ganha de graça por padrão

    • Saí de uma empresa que usava backend em Elixir para uma que usa Node.js. Eu quase não tinha usado Node.js antes, então estava neutro
      O projeto em que trabalhei era um pipeline de dados de backend, e nem processava tantos dados assim, mas isolar exatamente um bug central foi inacreditavelmente difícil
      Nesse processo, fui conhecendo várias características do Node.js e, comparando com Elixir/Erlang/OTP, cheguei à conclusão de que Node.js é menos confiável por design
      Também trabalhei bastante com Ruby e mexi com Python, mas muitas plataformas de linguagem da geração atual têm dificuldade para construir sistemas distribuídos confiáveis. A BEAM VM e a plataforma OTP já resolveram essa parte
    • Os blocos básicos de construção que Erlang e seus descendentes oferecem são fáceis de lidar e, por isso, também são fáceis de testar
      Por exemplo, o GenServer, que faz o papel de worker na maioria dos sistemas BEAM, no fim é uma estrutura que chama várias funções com parâmetros simples
      Portanto, é possível testar chamando essas funções diretamente, passando os parâmetros manualmente e validando apenas a saída. Não é preciso configurar um sistema de testes complexo para lidar com código assíncrono nem travar esperando a conclusão do trabalho nos testes
      É algo que juniores frequentemente deixam passar, mas, quando percebem, é bastante libertador
    • Nós C são subestimados. Uso nós C baseados em Cgo para comunicação entre serviços Go e Elixir rodando no mesmo pod Kubernetes. A documentação do Erlang e da biblioteca C também é bem decente
    • Tenho curiosidade sobre como a comunidade vê o motivo de o HN parecer gostar de Erlang/Elixir, mas isso não levar à adoção
  • Já vi algumas pessoas, principalmente gestores, dizerem que escreveriam um livro com base na nossa experiência. Sempre foi frustrante ver que cada um enxergava partes diferentes como o fator central do sucesso do nosso trabalho. O que eu sentia como essencial eles muitas vezes reduziam a algo apenas desejável
    Aqui também, alguém diz que processos leves e passagem de mensagens não são o molho secreto, mas deixa passar que a visão de Erlang como processos sequenciais comunicantes (CSP) não pode ser separada dessas características. Ainda assim, menciona repetidamente CSP como parte do molho secreto
    Por exemplo, que o programador de aplicação escreve código sequencial e a concorrência fica escondida dentro de behaviours; que a lógica de negócio é sequencial, facilitando a entrada de novos membros na equipe; ou que supervisors e a filosofia “let it crash” criam sistemas confiáveis
    Behaviours são interessantes, eram comuns nos anos 80 e resolvem um problema que alguns ainda atacavam nos anos 2000, mas em Erlang são tanto fim quanto meio. São a forma como essas outras características foram implementadas, mas não sei se necessariamente precisavam ser assim para Erlang continuar parecendo Erlang

    • Erlang não é CSP, é o modelo de atores. https://en.wikipedia.org/wiki/Actor_model
      CSP é a vertente que, passando por occam e outras linguagens, inspirou os canais de Go. A sincronização em canais sem buffer é a diferença mais evidente, e há outras, como pattern matching em caixas de correio no modelo de atores
      O debate entre CSP e modelo de atores parece parecido por fora, mas as implicações reais são bem diferentes, então é bastante interessante
    • Erlang é visto como CSP? Eu sempre achei que não, e sim como um modelo próprio em que atores com identificadores se comunicam diretamente. Eu via CSP como algo mais próximo de processos anônimos usando mensagens por canais
      Acho que o modelo de atores faz mais sentido, mas isso pode variar bastante de pessoa para pessoa
    • Gestores criam suas próprias narrativas conforme o clima
  • Entrei querendo saber mais sobre por que a Ericsson parou de usar Erlang e por que Joe foi demitido
    Em resumo, a Ericsson migrou novos projetos para Java, e isso parece ter marginalizado Erlang. Depois, Joe e colegas fundaram a Bluetail em 1998, que foi adquirida pela Nortel
    A Nortel era uma gigante de telecomunicações que respondia por cerca de um terço do valor da Bolsa de Toronto. Em 2000, suas ações chegaram a US$ 125 por ação, mas em 2002 caíram para menos de US$ 1. Foi parte do colapso das ponto-com e, combinado com a queda brusca nos gastos com telecomunicações, atingiu a Nortel de forma particularmente dura
    Parece mais seguro ver a demissão de Joe como uma situação em que “o departamento dele foi um dos primeiros a ser inundado em um navio afundando”. A Nortel demitiu 60 mil pessoas, ou seja, mais de dois terços de toda a força de trabalho. Essa demissão não era sinal de que Joe não estava fazendo seu trabalho, nem de ineficiência da unidade de negócios; foi uma medida desesperada e em massa

    • Nesse contexto, é muito estranho ver a palavra “fired”. “laid off” é mais apropriado
      “fired” carrega um juízo de valor forte e sugere demissão por justa causa, por alguma falha. Mesmo que isso tenha acontecido, o autor do post original não teria como saber, e nem caberia a ele saber
  • Comecei a olhar para Erlang de novo por causa dos processos leves e da passagem de mensagens, e até agora os behaviours eram algo secundário
    O projeto é trazer a programação visual baseada em fluxo (FBP) para Erlang. A FBP parece ter sido feita para Erlang, então foi surpreendente não encontrar uma implementação já existente
    A ferramenta que eu mais usava para FBP era o Node-RED, então a ideia básica é acoplar o frontend do Node-RED a um backend Erlang e transformar todos os nós em processos. O frontend do Node-RED é bom para modelar a passagem de mensagens entre nós, o que permite um mapeamento 1:1 muito simples para processos e mensagens do Erlang
    Implementei parte das funcionalidades básicas e comecei a criar fluxos como testes unitários para ir acumulando recursos aos poucos. Seria bom se ficasse 100% compatível com o Node-RED, que tem backend em Node.js. Mais detalhes estão no repositório do GitHub → https://github.com/gorenje/erlang-red
    No geral, Erlang se encaixa surpreendentemente bem nisso, e me espanta que ninguém tenha feito algo parecido. Será que já existia?
    [1] = https://jpaulm.github.io/fbp/index.html

    • Sempre achei que uma linguagem de programação visual sobre a BEAM seria interessante
    • Fico me perguntando se, em alto nível, dá para ver FBP como algo parecido com processos Erlang em que o fluxo de mensagens é unidirecional
  • Para mim, a força de Erlang/Elixir não está tanto na implementação do modelo de atores, no matching vindo do Prolog, na imutabilidade ou nos behaviours em si, mas na vontade do Joe de mostrar que é possível fazer mais com menos
    É um sistema computacional bem projetado e comprovado, com uma consistência rara de ver não só em outras linguagens, mas também no mundo “web”. Não é perfeito, mas é bem impressionante
    Infelizmente, acho que no mundo do software há pouco reconhecimento e pouca adoção do que a simplicidade torna possível. A complexidade faz as pessoas virarem especialistas, permite que gestores justifiquem equipes grandes e muitas reuniões, e mantém os especialistas como especialistas
    Erlang foi desenvolvido numa época em que empresas tentavam implementar soluções de software com menos gente e desempenho limitado. Nas décadas seguintes, com tanto dinheiro sendo despejado nessa área, o valor de “menos significa mais de uma forma boa para todos” acabou parecendo menos atraente

    • Alan Kay já disse que a simplicidade é obtida escolhendo blocos de construção um pouco mais complexos. Erlang parece fazer isso
    • Isso me lembra a palestra Simple VS Easy, do Rich Hickey
    • Esse comentário me deu vontade de dedicar mais tempo a Erlang. Mexi um pouco nele e, pelo menos superficialmente, gosto mais da sintaxe de Erlang do que da de Elixir
  • O conceito mais interessante em Erlang/BEAM é que a recuperação parcial vem embutida desde o começo
    Ao encontrar um estado inesperado, em vez de matar todo o processo ou continuar e aceitar a corrupção, ele volta para um estado conhecido e correto no nível mais granular possível
    Essa ideia foi pesquisada há muito tempo com o nome de “microreboots” e também tinha relação com “crash-only software”, mas só Erlang/BEAM a tornou um conceito de primeira classe do sistema operacional

    • Ainda é preciso tomar cuidado com árvores de supervisão e com reiniciar partes da árvore. Por exemplo, se todo o processo do sistema operacional Erlang morrer de repente e reiniciar, o sistema pode funcionar normalmente; mas, se apenas uma parte da árvore de processos Erlang for reiniciada, os dados podem começar a ser corrompidos
      Erlang oferece um bom modelo para lidar com esses problemas, mas isso não significa que você possa desligar completamente o cérebro. Se entrar achando que é só deixar reiniciar e tudo ficará bem, pode se queimar
    • Fico curioso sobre como essa explicação difere de tratamento de exceções
  • Erlang, OTP e BEAM oferecem mais do que behaviours. A VM é quase um kernel virtual, com supervisors, processos isolados e um modo distribuído que trata várias máquinas físicas/virtuais como um único pool de recursos
    O OTP também oferece vários modos úteis, como o banco de dados Mnesia, contadores atômicos para cache e tabelas ETS. O runtime também suporta recarregamento a quente de bytecode, permitindo aplicar patches sem interromper o sistema. A sintaxe não é muito amigável para leitores de tela, mas é legível
    A única plataforma que me vem à mente como parecida com a BEAM na capacidade de tratar recursos de várias máquinas como um único pool é o Apache Mesos[1]
    Há cerca de um ano, minha empresa pessoal de consultoria adotou Erlang como linguagem de backend. Desde então, comecei a explorar o interior da BEAM, por exemplo trocando uma stack baseada em TCP por QUIC ou integrando patches em Rust
    É uma escolha realmente excelente para sistemas leves e de alta vazão que não falham a menos que haja kernel panic ou perda de energia. Atualmente estamos criando software muito ocupado e altamente concorrente, como rastreadores de produção de filmes/jogos e gerenciadores de pipeline, e também preparando P&D para um serviço privado de gestão hospitalar
    [1]: https://mesos.apache.org/

    • Não pretendo adotar completamente Elixir ou Gleam, porque esse ecossistema foi construído em torno do framework Phoenix e de serviços/bancos de dados externos
      É bem provável que tenhamos de manter internamente bindings ou implementações para coisas que não são mantidas no lado do Elixir
      Além disso, há muito açúcar sintático, e os usuários demonstram uma obsessão peculiar por abstrair coisas em interfaces de DSL
  • Há um motivo por trás da pergunta: “por que designers de linguagens e bibliotecas só copiam a ideia de processos leves e passagem de mensagens, mas não roubam a estrutura por trás dos behaviours do Erlang?”
    As assinaturas de função dos behaviours do Erlang estão fortemente ligadas a outros recursos do Erlang, especialmente ao seu modo peculiar de usar imutabilidade. É por isso que o servidor precisa de uma chamada init separada, e a gestão de estado também precisa ter uma forma muito clara para funcionar do mesmo jeito
    Mas, em outras linguagens, para alcançar o mesmo objetivo, quase sempre há muitos casos em que não se deve simplesmente copiar o que o Erlang faz. Se alguém diz que “portou o gen_server para outra linguagem” e mostra exatamente a mesma interface do Erlang, vejo isso como um porte que não entende profundamente o que o Erlang faz
    Quando levei a ideia de supervisor tree para Go[1], fiz de um jeito idiomático para Go. Em Go moderno, a interface correta para algo “supervisionável” não é uma assinatura como a do Erlang, mas apenas isto
    type Service interface {
    Serve(context.Context)
    }
    Em Go, isso é tudo, e é só isso que se deve usar. Em outras linguagens pode ser diferente. Go tem canais, então não precisa de “handle_event/2”, e deve usá-los. Não porque canais sejam melhores ou piores, mas porque a linguagem funciona assim
    Em outras linguagens, pode-se usar outra coisa; em outras infraestruturas, em vez de “chamar handle_event/2”, também se pode enviar para Kafka ou para um barramento de eventos em nuvem. O ponto central é criar um sistema orientado a eventos, não copiar a implementação exata do Erlang
    Na comunidade Erlang existe um problema de confiança excessiva de que há algo tremendamente especial no modo exato como o Erlang faz as coisas, e que fazer diferente é automaticamente errado e não confiável. Talvez fosse assim em 2005, mas não em 2025
    Houve uma época em que Erlang era praticamente a única resposta que fazia sentido, mas o problema de 2025 é navegar por uma enxurrada de respostas. Recomendo fortemente aprender com Erlang para criar software confiável, mas sou fortemente contra portar cegamente para outras linguagens o modo exato como Erlang atingiu isso. Em quase todos os outros contextos de linguagem, essa é a resposta errada. Mesmo outras linguagens imutáveis têm estruturas suficientemente diferentes para que não dê para copiar literalmente
    [1]: https://jerf.org/iri/post/2930/

    • A próxima pergunta natural, então, me parece ser: “por que outras linguagens e sistemas com essa abordagem e essas ideias tiveram sucesso de adoção no mercado, enquanto Erlang/Elixir não?”
      Como alguém que usa Elixir profissionalmente, essa é para mim a pergunta mais interessante sobre Erlang
      Vendo o grande sucesso de k8s, Kafka e dos produtos de sistemas distribuídos da AWS, fica claro que há uma demanda enorme por ferramentas que ajudem a projetar sistemas concorrentes confiáveis. Então por que Erlang/Elixir não capturou essa fatia?
      Discuto isso com frequência com amigos, mas não sei a resposta
    • Go é minha linguagem favorita, mas tenho dúvidas sobre a afirmação de que “Go tem canais, então não precisa de handle_event/2”. Que tipo de canal? Mais importante: canais são locais ao processo, então para colocá-los na rede é preciso código de cola
      Imagino que Erlang tenha abstraído o tratamento de mensagens de rede. Além disso, já vi umas 3 ou 4 variações do padrão que você propôs para coisas como servidores de longa duração
      Concordo totalmente que, ao portar, é preciso usar componentes idiomáticos. Mas uma linguagem pode ter mecanismos ocultos que fazem perder, no processo de porte, a essência valiosa. Dá para chamar isso de uma espécie de antirrelativismo de linguagens de programação
      “Canais? É só envolver com X” também pode ser muito mais prejudicial à interoperabilidade do que parece. Por exemplo, olhando para http.Handler em Go, ele é simples, mas o fato de estar na biblioteca padrão tem grandes implicações práticas. Sem coordenação prévia, surge um ecossistema de middlewares em geral compatíveis entre si
      io.Reader e seus parentes são parecidos. Dá para dizer que essas interfaces extremamente simples são mais valiosas do que as implementações
      É especulação, mas, se Erlang acertou muitas interfaces para sistemas distribuídos confiáveis, talvez isso tenha sido justamente o fator que tornou o todo possível
  • Discordo. Interfaces são um conceito trivial que pode ser anexado a qualquer linguagem. Mesmo em linguagens sem sintaxe oficial de interfaces, dá para imitá-las no espaço do programa
    A BEAM teve sucesso porque consegue rodar 1 milhão de processos em um único nó, expressar facilmente máquinas de estado distribuídas complexas e reiniciar partes do sistema sem interrupção. Há muitos outros motivos também
    Realmente não acho que behaviour/interface seja a peça mais central

    • Concordo em certa medida. Quando se olha para Erlang pela primeira vez, parece que, para cada recurso que Erlang tem, existe algo parecido em outros sistemas
      Heaps de processos isolados podem ser obtidos usando processos do sistema operacional; supervisor trees podem ser feitas com Kubernetes; passagem de mensagens pode ser criada em Java com duas threads e uma fila compartilhada; hot code loading também é possível em Java; e, para baixa latência, talvez dê até para superar Erlang ajustando o LMAX Disruptor
      Mas o ponto central é reunir tudo isso em uma única plataforma ou biblioteca. Processos do sistema operacional são pesados, e não é fácil rodar 2 milhões deles em um servidor. Se você usa green threads ou promises, perde o heap isolado
      Kubernetes também pode ser usado até certo ponto, mas não lida bem com supervisor trees aninhadas. Talvez seja possível, mas além do código surgem pods, controllers, volumes e todo tipo de coisa
      É possível fazer passagem de mensagens com bibliotecas de atores em várias linguagens, mas elas não integram de forma transparente coisas como fazer pattern matching no receive ou enviar para outra thread em outro nó
      Hot code loading também é possível, mas o problema é como lidar com estruturas de dados e estado em tempo de execução. Erlang foi construído em torno disso, e, como em gen_server o estado é imutável e explícito, ele tem callbacks para atualizar não só o código, mas o próprio estado
    • Não usei o bastante, mas acho que a BEAM evita em parte o problema redescoberto por Ian Cooper em “Where Did It All Go Wrong?”, isto é, o fato de microserviços não otimizarem o atrito dentro dos módulos e entre módulos de um sistema
      Não diria que a BEAM elimina esse problema, mas parece reduzir a inclinação da curva. Idiomas e recursos autoconsistentes, incluindo deploy, recuperação automática e balanceamento de carga, reduzem o atrito entre módulos
      Ela permite criar um sistema em que 12 engenheiros conseguem gerenciar facilmente 30 endpoints, e cuja área de superfície ainda pode seguir uma lei de potência
  • Não concordo com o conteúdo deste texto. Behaviours são possíveis por causa da arquitetura de base do sistema
    Behaviour não é uma interface; está mais perto de um objeto abstrato em linguagens como Java. Ele implementa uma funcionalidade básica e autocontida por trás de uma interface de colaboração, mas não consegue fazer muita coisa sem a infraestrutura subjacente que garante que cada processo seja completamente separado dos outros, que todos os processos possam ser encerrados com segurança sem vazamentos de memória ou recursos, e que não seja possível compartilhar ponteiros perigosos entre dois processos
    O que Joe mostrou no artigo foi como construir um sistema confiável, e até certo ponto um sistema distribuído confiável, a partir de um determinado conjunto de blocos de Lego
    Para implementar isso corretamente, é preciso a VM do Erlang, e não dá para implementar completamente em outras VMs. Sem a tubulação de baixo nível, as árvores de supervisão vazam. Em Java, não dá para matar uma thread que está segurando recursos e esperar que tudo sempre dê certo, e também faltam formas de monitorar processos diferentes