Artigos aprofundados sobre a tecnologia que molda o que vem a seguir.

IRCv3: Lições de Design de Protocolo Retrocompatível

O IRCv3 moderniza um protocolo de 1988 sem quebrá-lo. Lições sobre negociação de capacidades, message tags e evolução retrocompatível de protocolos.

Uma máquina de telégrafo vintage conectada por um cabo luminoso a um smartphone moderno sobre uma mesa
O truque do IRCv3: o formato de 1988 continua funcionando, mas clientes modernos ganham uma experiência moderna.

Em 1988, um estudante universitário finlandês escreveu um protocolo de chat que cabia em algumas poucas páginas. Quase quatro décadas depois, esse mesmo protocolo — o IRC — ainda transporta milhões de mensagens por dia, e o grupo de trabalho do IRCv3 está, discretamente, ensinando o resto de nós a evoluir um protocolo sem incendiar a base instalada. Se você já tentou versionar uma API da qual desconhecidos dependem, o IRCv3 é o estudo de caso que você deveria copiar.

Já construí bots e clientes experimentais para IRC, de vez em quando, ao longo dos anos, e o que continua me surpreendendo é o quão moderna a experiência pode parecer quando os dois lados falam as extensões mais novas. Histórico reproduzido pelo servidor. Autenticação de verdade. Timestamps de mensagens que sobrevivem a um bouncer. E, ainda assim, um cliente escrito em 1995 consegue se conectar ao mesmo servidor e conversar normalmente. Essa combinação não acontece por acaso — é resultado de um conjunto de decisões de design deliberadas e de alguns erros dolorosos que a comunidade precisou contornar.

O pecado original: capacidades que você não consegue perguntar

O protocolo IRC básico, codificado na RFC 1459 e depois na RFC 2812, não tinha nenhum mecanismo para um cliente perguntar ao servidor o que ele suporta. Nenhum. Você se conectava, enviava NICK e USER e, então, chutava. Quer saber se o servidor suporta determinado modo ou extensão? Envie e veja se volta um erro. Isso soa pitoresco até você ter mantido uma biblioteca cliente que precisa tratar casos especiais para cinco implementações de servidor diferentes, cada uma interpretando de forma levemente diferente as partes ambíguas da especificação.

O modo de falha aqui não é teórico. Desenvolvedores escreviam clientes que descobriam funcionalidades disparando comandos e analisando numéricos de erro — uma bagunça frágil que quebrava sempre que um servidor mudava uma string de erro. Bots entravam em canais com premissas erradas e inundavam o log de falhas. Este é o abismo entre especificação e implementação em seu habitat natural: quando a especificação não oferece um mecanismo de descoberta, cada implementador inventa o seu, e mal.

A correção do IRCv3 foi a negociação de capacidades, e é tão simples que chega a ser constrangedor. Durante o registro da conexão, o cliente envia CAP LS e o servidor responde com uma lista de capacidades nomeadas que suporta — coisas como sasl, server-time ou message-tags. O cliente então envia CAP REQ com exatamente o subconjunto que deseja, o servidor confirma, e ambos seguem com um entendimento compartilhado e explícito do que a sessão suporta. Um cliente legado que nunca envia CAP simplesmente recebe o comportamento antigo. Um servidor legado que não entende CAP devolve um erro que o cliente pode ignorar com segurança e usar como fallback.

-> CAP LS 302
<- CAP * LS :sasl=PLAIN,EXTERNAL server-time message-tags echo-message
-> CAP REQ :sasl server-time
<- CAP * ACK :sasl server-time
-> AUTHENTICATE PLAIN
<- AUTHENTICATE +
-> AUTHENTICATE dXNlcgB1c2VyAHBhc3N3b3Jk
<- 900 jane jane!jane@host jane :You are now logged in

A lição vai muito além do IRC. Se você está projetando qualquer protocolo ou API hoje, construa o mecanismo de negociação já na versão um, mesmo que ainda não haja nada para negociar. O custo é uma única troca de mensagens; o retorno é que toda funcionalidade futura se torna opt-in em vez de uma quebra de compatibilidade.

Lição: extensibilidade vence novos comandos

A segunda grande ideia do IRCv3 são as message tags, e ela nasceu de um erro real: as pessoas continuavam tentando adicionar funcionalidades definindo novos comandos ou enfiando semântica nos parâmetros de mensagens existentes. O formato de fio do IRC é notoriamente enxuto — um prefixo, um comando e uma lista de parâmetros finais. Não havia onde pendurar metadados. Então, quando alguém queria timestamps em histórico reproduzido, ou marcar qual conta enviou uma mensagem, a resposta da velha escola era inventar um novo numérico ou desfigurar o texto final. As duas abordagens quebram clientes antigos ou obrigam todo parser a crescer com casos especiais.

A solução foi acrescentar uma seção nova e opcional no início da mensagem: @chave=valor;chave2=valor2 antes do prefixo. Crucialmente, os servidores só enviam tags para clientes que negociaram a capacidade message-tags, então clientes antigos nunca veem um byte que não sabem interpretar. Eis como uma mensagem com timestamp realmente aparece no fio:

@time=2024-03-14T09:26:00.000Z;account=jane :jane!jane@host PRIVMSG #rust :borrow checker finally clicked for me

É o mesmo movimento arquitetural dos cabeçalhos HTTP, dos metadados do gRPC ou da tolerância a campos desconhecidos do protobuf: reservar um ponto de extensão genérico e as funcionalidades deixam de exigir cirurgia no protocolo. A extensão server-time, que resolve o velho problema do bouncer em que todas as mensagens reproduzidas parecem ter acabado de chegar, é literalmente uma tag. A draft +reply, que permite aos clientes encadear respostas a mensagens específicas, é outra tag. Nenhum comando novo. Nenhum numérico novo. Nenhum dia D de atualização obrigatória.

Eu mesmo cometi o erro oposto. Num sistema interno de pub/sub, anos atrás, adicionamos uma funcionalidade sobrecarregando um campo JSON com um prefixo mágico, porque mudar o formato do envelope "dava trabalho demais". Oito meses depois, estávamos mantendo uma expressão regular em três linguagens para desembrulhar a mágica, e o parser de uma equipe tratou a string mágica como dado literal, o que gerou um incidente memorável. Um campo de metadados simples e explícito teria custado um dia. A string mágica nos custou trimestres. As message tags do IRCv3 são exatamente o que aquele sistema deveria ter tido desde o começo.

Envelopes numa esteira de transporte, alguns carregando pequenos adesivos coloridos de metadados
Message tags: metadados opcionais anexados a cada mensagem, invisíveis para clientes que nunca os pediram.

Lição: acabar com o contorno de senha em texto puro

Pergunte a qualquer pessoa que usou um cliente IRC nos anos 2000 como funcionava o login e você ouvirá a mesma resposta: você digitava /msg NickServ IDENTIFY senha. Suas credenciais saíam como uma mensagem de chat comum, ficavam visíveis em logs e, às vezes, eram coladas num canal público quando o cliente falhava no comando — um rito de passagem tão comum que virou meme. A autenticação foi parafusada na camada de aplicação porque o protocolo não tinha noção alguma dela.

O IRCv3 resolveu isso de forma adequada ao padronizar a autenticação SASL durante o registro. Em vez de conversar com um bot depois de conectar, o cliente negocia a capacidade sasl e se autentica como parte do handshake, antes de ter um apelido que alguém possa ver. O servidor sabe quem você é desde a primeira mensagem, o que reverbera em tudo: a extensão account-tag pode anexar o nome da sua conta a cada mensagem, bots podem tomar decisões de permissão sem heurísticas próprias para interpretar o NickServ, e clientes podem construir fluxos de registro que não exigem ler um muro de texto em NOTICE.

A lição mais ampla: quando um contorno se torna universal, ele deixa de ser contorno e vira seu protocolo de fato — sem nenhum do cuidado de design. O NickServ não estava em nenhuma RFC, mas moldou duas décadas de comportamento dos clientes, incluindo suas falhas. Padronizar aquilo que as pessoas já faziam (autenticar ao conectar) com um mecanismo de verdade (SASL, que traz PLAIN, EXTERNAL para certificados de cliente e espaço para mais) transformou folclore em infraestrutura. Se seus usuários estão todos fazendo a mesma dança desajeitada para conseguir algo, esse é o próximo item da sua especificação, não uma FAQ de suporte.

Lição: histórico é um problema de protocolo, não de cliente

Durante a maior parte da vida do IRC, o protocolo foi estritamente ao vivo: se seu cliente não estava conectado quando uma mensagem era enviada, aquela mensagem simplesmente nunca existiu para você. A resposta da comunidade foi o bouncer — um proxy permanentemente conectado (o ZNC é o clássico) que ficava numa VPS, mantinha sua sessão e reproduzia o que você perdeu. Os bouncers funcionavam, mas eram mais uma peça de infraestrutura para rodar, mais uma credencial para gerenciar, e a experiência de reprodução era péssima: tudo chegava carimbado com a hora da reprodução, fora de ordem em relação ao que você tinha visto ao vivo.

O IRCv3 atacou isso em camadas. Primeiro, a server-time tornou o histórico reproduzido utilizável ao carregar o timestamp real de recebimento. Depois, a draft chathistory deu aos clientes uma forma padrão de solicitar intervalos do histórico, e servidores como o Ergo passaram a armazenar o histórico dos canais diretamente. O resultado, como muita gente descobriu ao subir um pequeno servidor Ergo para amigos, é impressionante: conecte pelo celular depois de uma semana offline e o servidor reproduz tudo com os timestamps corretos, sem bouncer, sem VPS para cuidar. É o maior salto de qualidade de vida que o IRC já teve, e veio do protocolo, não de um truque esperto no cliente.

Existe um contra-argumento que merece ser levado a sério, e usuários diários de IRC o fazem: a maior parte da aspereza percebida no IRC está no cliente, e dá para ter uma boa experiência hoje com um cliente decente e um bouncer. É verdade — mas esse argumento prova o ponto pelo outro lado. Cada autor de cliente reimplementou independentemente a costura do histórico, o rastreamento de menções e a deduplicação de reprodução, com bugs sutilmente diferentes. Mover a parte difícil para o protocolo e o servidor significa que ela é implementada uma vez, corretamente, e todos os clientes se beneficiam. Fazemos essa mesma escolha o tempo todo no desenvolvimento web: empurrar invariantes para a plataforma (pense em quanto código de aplicação desapareceu quando os navegadores ganharam fetch nativo, ou quando o TLS virou requisito básico) em vez de deixar mil bases de código resolverem o problema pela metade.

Lição: funcionalidades opcionais criam realidades divergentes

Agora, o erro que o IRCv3 ainda não resolveu por completo — e é o mais relevante para quem projeta sistemas extensíveis. Como cada extensão é opcional e negociada de forma independente, dois usuários no mesmo canal podem literalmente ver conversas diferentes. Um cliente recebe mensagens com tags +reply e as exibe como respostas encadeadas; outro mostra as mesmas mensagens como linhas planas, sem contexto. Um usuário vê reações com emoji como glifos organizados presos a uma mensagem; outro não vê nada, ou um fallback enigmático. A matriz de suporte do IRCv3 é uma grade de sims e nãos, e a sua experiência depende da coluna em que você está.

Esse é o lado sombrio da degradação graciosa. A retrocompatibilidade garantiu que nada quebre, mas nada no modelo garante coerência. Slack e Discord, seja lá o que você pense deles, nunca têm esse problema: a plataforma controla as duas pontas com firmeza suficiente para que uma reação seja uma reação para todos. O IRCv3 escolheu evolução em vez de controle, e o custo é um cenário de funcionalidades permanentemente desigual, em que drafts às vezes continuam drafts por anos.

Se você está construindo algo extensível, reserve orçamento para isso desde o primeiro dia. Algumas mitigações que o IRCv3 e ecossistemas semelhantes adotaram:

  • Projete as degradações deliberadamente. Uma tag de resposta deve cair para uma linha citada legível, não sumir. Se o fallback não for utilizável, a funcionalidade não está pronta para ser lançada como opcional.
  • Publique a matriz de suporte e mantenha-a honesta. As tabelas de clientes e servidores do IRCv3 permitem que os usuários façam escolhas informadas; o silêncio atrai culpa para o software errado.
  • Defina uma base e promova-a. As versões numeradas (3.1, 3.2) agrupam capacidades para que clientes mirem em "suporte 3.2" em vez de onze flags individuais. Dê ao seu ecossistema um piso para convergir.
  • Expire seus drafts. Um draft que é distribuído em servidores mas nunca é finalizado — o histórico de canais já flertou com esse destino — é pior do que não ter draft, porque bifurca o comportamento sem se comprometer.

Vimos uma versão disso no artigo sobre por que a edição colaborativa é mais difícil do que parece: no momento em que os participantes podem discordar sobre a forma do estado compartilhado, você precisa de regras explícitas de convergência, não apenas de boas intenções. Opcional por padrão é uma boa estratégia para capacidades, mas a semântica visível ao usuário precisa de um plano para as pessoas presas do outro lado da matriz.

Duas janelas estilizadas mostrando o mesmo fluxo, uma rica em threads e símbolos, a outra simples
Extensões opcionais significam que dois usuários podem acompanhar o mesmo canal e ver conversas diferentes.

O que isso significa para sua próxima API ou protocolo

Tirando a nostalgia do chat, o IRCv3 é um playbook compacto para evoluir qualquer coisa que tenha um formato de fio. Aqui está a checklist que eu entregaria a qualquer time que esteja começando um protocolo, um schema de eventos ou até uma API REST pública:

  1. Entregue a negociação primeiro. Troca de capacidades já na versão um, mesmo com uma lista vazia. Adicionar descoberta depois é dez vezes mais difícil.
  2. Reserve um ponto de extensão. Tags, cabeçalhos, mapas de metadados — um slot genérico onde funcionalidades futuras possam se pendurar sem tocar na gramática central.
  3. Torne o comportamento novo opt-in por conexão. Clientes antigos devem receber exatamente o comportamento antigo, byte a byte. Servidores IRCv3 nunca enviam uma tag para um cliente que não pediu.
  4. Padronize o folclore. Qualquer gambiarra em que seus usuários convergiram (autenticação no estilo NickServ, reprodução no estilo bouncer) é uma especificação esperando para ser escrita direito.
  5. Planeje a matriz de suporte. Funcionalidades opcionais divergem; decida como ficam as experiências degradadas e publique quem suporta o quê.
  6. Agrupe o progresso em marcos nomeados. Flags individuais são para implementadores; versões numeradas são para humanos.

O que considero genuinamente impressionante é a contenção. O IRCv3 não correu atrás de criptografia de ponta a ponta, de uma redesenho da federação ou de qualquer reescrita grandiosa. Escolheu os problemas pequenos e afiados — descoberta, metadados, autenticação, tempo — e os resolveu de maneiras que um cliente de 1995 pode simplesmente ignorar. É por isso que o IRC é praticamente o único sobrevivente da sua geração de protocolos da internet ainda rodando em grande parte como foi projetado, e por que um projeto de fim de semana hoje pode dar a um grupo de amigos sincronização de histórico, TLS e contas reais numa VPS de 5 dólares.

Retrocompatibilidade não é a ausência de mudança. É a disciplina de fazer de cada mudança algo que o passado possa ignorar com segurança.

Da próxima vez que você for tentado a versionar uma API trocando o caminho de /v1 para /v2 e forçando todos os consumidores a migrar, lembre-se do protocolo que permitiu a um cliente da era dial-up e a um app móvel moderno compartilharem o mesmo canal. Negocie capacidades. Anexe metadados em vez de inventar comandos. Mantenha o piso sólido e deixe o teto subir no seu próprio ritmo. O IRCv3 prova que é possível ter um protocolo que respeita seus veteranos e ainda aprende truques novos — e se um sistema de chat de 1988 consegue isso, seu microsserviço de três anos não tem desculpa.