Apagando uma integração de publicação que eu tinha acabado de construir
Algumas semanas atrás, conectei o Hashnode ao blog-manager: um cliente GraphQL, um criador de rascunho, um job de sincronização de estado publicado, um publisher, colunas em duas tabelas, campos de formulário, badges de status, um cron noturno. Essa semana apaguei tudo isso. Aqui está o motivo, e o que remover uma funcionalidade me ensinou sobre a forma que eu realmente quero.
O que eu estava tentando fazer
O blog-manager sindica meus posts do GitHub para outras plataformas. O plano original era “uma integração nativa por plataforma”: Medium, Dev.to, Hashnode, LinkedIn, cada uma com seu próprio cliente de API e seu próprio fluxo de publicação. É um modelo mental limpo até você ter quatro delas e perceber que são 80% o mesmo código com nomes de campo diferentes.
Aí adicionei o Postiz (um agendador social auto-hospedado) para o lado social, e a duplicação piorou. Um post do blog agora podia ir para o LinkedIn de duas formas: nativamente, ou como um canal do Postiz. Eu já tinha arrancado o caminho nativo do LinkedIn exatamente por esse motivo. O Hashnode era a próxima peça do dominó. Eu nunca conectei uma conta real do Hashnode, então era peso morto puro, código que eu teria que manter verde em todo refactor futuro para uma plataforma que eu não uso.
O que eu removi
O grep foi a contabilidade honesta. 31 arquivos tocavam em hashnode:
app/services/hashnode/{client,draft_creator,published_sync,publisher}.rb
app/jobs/hashnode_{import,sync}_job.rb
lib/tasks/hashnode.rake
+ columns on posts (7) and blogs (3), two indexes
+ enums, controller actions, routes, a nightly cron
+ form sections, table columns, filters, a status-badge preset
+ all the tests for the above
Diff final: 31 arquivos, +14 / -1126. Apagar funcionalidades é a única vez em que a contagem de linhas se move na direção satisfatória.
A migração espelhou a do LinkedIn: um remove_column/remove_index reversível com todos os argumentos de tipo, então ela reverte de forma limpa:
def change
remove_index :posts, :hashnode_status
remove_index :posts, :hashnode_import_state
remove_column :posts, :hashnode_status, :integer, default: 0, null: false
# ...7 post columns, 3 blog columns
end
Decisões que tomei pelo caminho
O grep mentiu um pouco, e essa é a parte interessante. Minha primeira varredura filtrava por extensão de arquivo: .rb, .erb, .yml, .md. Ela perdeu lib/tasks/hashnode.rake porque .rake não estava na minha lista. Só peguei isso numa segunda passada, sem filtro, no final. Lição que continuo reaprendendo: quando você está removendo algo “por inteiro”, o grep de verificação precisa ser mais amplo que o grep de edição. A coisa que você esquece de procurar é a que sobrevive.
Uma decisão que o ticket explicitamente deixou em aberto: uma constante chamada NATIVE_BLOG_PROVIDERS exclui Medium/Dev.to/Hashnode do seletor de canais do Postiz, para que um post não possa ser cross-posted duas vezes (artigo completo nativamente + um texto curto via Postiz). Com o Hashnode nativo fora, hashnode devia continuar nessa lista de exclusão? Eu removi. Não existe mais risco de post duplicado, e tirar ele significa que, se eu algum dia conectar um canal do Hashnode no Postiz, ele fica selecionável como qualquer outro alvo social. Essa escolha meio que já mostra para onde tudo isso está indo.
A especificação do ticket era uma sugestão, não um mapa. Ela listava colunas e arquivos para apagar, mas foi escrita antes de uma feature posterior (o seletor de fonte de URL) que, desde então, tinha adicionado hashnode_url ao SHARE_SOURCES, shareable_urls e share_url do model Post. Nada disso estava na checklist de remoção. “Remover por inteiro” só funciona se você recalcular o raio de impacto a partir do código atual, não de um ticket escrito contra uma árvore mais antiga.
O que me surpreendeu
Remover o Hashnode não pareceu limpeza. Pareceu o código me fazendo uma pergunta que eu vinha evitando: por que eu tenho publishers nativos, afinal?
No momento, Medium e Dev.to ainda publicam nativamente: artigo completo, meu próprio cliente de API, minha própria dança de rascunho-depois-publica. Tudo que é social passa pelo Postiz. Essa divisão fazia sentido quando eu construí ela (“plataformas de blog nativas, social via Postiz”), mas depois de apagar o Hashnode eu não consigo mais defender a fronteira de forma limpa. Não é “blog vs social”, é “as duas que eu construí primeiro vs todo o resto”. Isso é história, não arquitetura.
O que vem a seguir
Vou abrir um spike para descobrir o fluxo certo de publicação e divulgação, de ponta a ponta: quais plataformas continuam nativas e por quê, quais migram para trás do Postiz, e o que a ação de publicar deveria sequer significar quando um post se ramifica em uma URL canônica mais N cross-posts mais M textos sociais. Apagar uma integração foi fácil. Decidir o que as que sobraram deveriam ser é o trabalho de verdade. Mais sobre isso quando o spike terminar.
Leitura relacionada
Add a feature, or move a responsibility?
Adding Postiz social cross-posting looked done until a blunt question exposed a double-post bug, and a full audit of every posting path in the app found two more like it.
O webhook do Postiz que eu não consegui construir, e o polling em vez dele
Um spike que terminou em "não construa isso": rejeição de IP privado, um payload vazio sem autenticação, e a pull API que tinha tudo que a push API não tinha.
Agendamento e compartilhamento de link mais inteligente no meu cross-poster do Postiz
Agendamento para o futuro, blurbs mais ricos com thumbnails, um seletor de qual URL usar, além do bug de timezone no horário de parede e a importação que copiou a coluna errada.
Você também pode achar útil
AdGuard para iOS
Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.
Como afiliado da AdGuard, ganho com compras qualificadas.
Saiba maiseSIM Airalo
eSIM de dados local para viagens - sem necessidade de trocar um SIM físico.
Este é meu link de indicação da Airalo. Você recebe um desconto no seu primeiro eSIM e eu ganho crédito da Airalo para o meu.
Saiba maisNordPass
Gerenciador de senhas da equipe por trás da NordVPN, com um plano gratuito.
Como afiliado da NordPass, ganho com compras qualificadas.
Saiba mais