Pular para conteúdo

🏗️ 6 dias, 36 PRs, 3 repos novos: desmembrando o monolito data-platform

Na quinta passada, o data-platform era um monolito: scrapers, DAGs, sync de datasets e integração com LLM — tudo num repo só 😅. Hoje, uma semana depois, cada domínio vive no seu repo, deploya suas próprias DAGs e tem autonomia de release. Pelo caminho, o servidor ActivityPub ganhou pipeline de publicação e entramos no Fediverso 🌐. Foram 36 PRs mergeados em 9 repos, 3 repos novos criados, 4 issues fechadas e 5 avançadas — tudo sem parar o pipeline em produção.

Este post conta essa história: as decisões, os erros, as esperas de 20 minutos ⏳ e o momento em que percebemos que estávamos codificando um padrão novo.


🗺️ Contexto: o que é o DGB

O Destaques Gov BR agrega notícias de ~158 órgãos do governo federal. Um scraper coleta as publicações, um pipeline enriquece via LLM (classificação temática, embeddings), e um portal público exibe tudo com busca semântica. A infraestrutura roda na GCP: Cloud SQL (PostgreSQL), Cloud Composer (Airflow), Cloud Run e Typesense.

Até a semana passada, quase toda essa lógica vivia em um único repositório: data-platform. 👀


☀️ Domingo: scraper no Airflow (e a frustração que mudou a arquitetura)

Tudo começou com a issue data-platform#57 — "Migrar Scraper para Airflow com DAG por Órgão". O scraping rodava via GitHub Actions, sequencial, uma vez por dia. O plano (MIGRAR-SCRAPER-AIRFLOW.md) desenhava DAGs dinâmicas, uma por agência, com schedule de 15 minutos.

Os primeiros PRs foram direto ao ponto: data-platform#76 migrou o scraper para o Airflow. E aí começaram os fixes — #77, #78, #79 — cada um corrigindo algo que só aparece em produção: formato do requirements.txt, ordem dos argumentos do gsutil rsync, dependências faltando 🤦. O Composer é implacável: cada atualização de requirements leva 10-20 minutos. Deploy, esperar, descobrir o erro, fix, esperar de novo.

Foi justamente nessas esperas de 20 minutos que veio a percepção mais importante da semana 💡: instalar o scraper inteiro como dependência do Composer é frágil. O Composer deveria orquestrar, não executar. O plano SCRAPER-CLOUD-RUN-API.md redesenhou tudo:

flowchart LR
    subgraph Composer["☁️ Cloud Composer (Airflow)"]
        DAG["🔄 DAG scrape_mec\n2 linhas de HTTP"]
    end
    subgraph CloudRun["🐳 Cloud Run (scraper-api)"]
        API["POST /scrape/agencies\nScrapeManager.run()\n→ PostgreSQL"]
    end
    DAG -->|"HTTP POST\nBearer IAM 🔐"| API

DAGs viram chamadas HTTP de duas linhas. Workers do Airflow ficam leves. Scraper roda em container isolado com scale-to-zero 🎯. Um PR no data-platform (#80) e outro no infra (infra#78) para o Terraform, e a nova arquitetura estava rodando.


📦 Segunda: nasce o repo scraper

Com a API funcionando no Cloud Run, o scraper já não pertencia ao data-platform. O plano EXTRAIR-SCRAPER-REPO.md mapeou a cirurgia: novo repo, Workload Identity Federation, deploy de DAGs em subdiretório separado no bucket GCS do Composer.

🆕 Novo repo: destaquesgovbr/scraper

A extração exigiu mudanças coordenadas em três repos simultâneos. No infra: WIF binding (infra#82), ajuste de concorrência (infra#80), remoção do pandas do Composer (infra#83). No data-platform: migrar DAGs para subdiretório (#81), remover código extraído (#82), limpar step de requirements (#83). No scraper: CLAUDE.md documentando tudo (scraper#1).

O padrão ficou claro: cada repo deploya suas DAGs num subdiretório próprio do Composer via gsutil rsync. O data-platform usa data-platform/, o scraper usa scraper/. Simples, mas precisava funcionar com Workload Identity Federation, permissões GCS e GitHub Actions — tudo coordenado 🤝.


🔧 Terça: reusable workflows, data-publishing e o padrão que virou skill

♻️ DRY no deploy

Com dois repos deployando DAGs no mesmo bucket, copiar o workflow de deploy era questão de tempo. Antes de ter um terceiro, criamos o reusable workflow (reusable-workflows#3): qualquer repo chama com 2-3 parâmetros e ganha deploy no Composer. A adoção foi imediata — data-platform#85, scraper#2 e scraper#3 migraram no mesmo dia.

📤 Segundo desmembramento: data-publishing

O plano PLAN-data-publishing-migration.md extraiu a DAG de sync PostgreSQL → HuggingFace para um repo próprio. Isso resolveu, por um caminho inesperado, a issue data-platform#28 — que pedia KubernetesPodOperator para isolar dependências pesadas. A solução acabou sendo mais elegante: repo dedicado com plugins no Composer, sem Kubernetes ✨.

🆕 Novo repo: destaquesgovbr/data-publishing

Os PRs de finalização limparam o data-platform: remoção da DAG de teste (#86), remoção do sync HuggingFace (#87), WIF no infra (infra#85), e atualização da documentação (docs#31).

🧠 O momento meta: codificando o padrão

Com três repos seguindo o mesmo padrão de DAGs (scraper, data-publishing, activitypub-server), ficou claro que tínhamos uma convenção madura. Em vez de deixar esse conhecimento implícito, codificamos tudo numa skill do Claude Code 🤖:

data-platform#88/criar-dag, um guia de ~470 linhas que inclui: referência completa da arquitetura Airflow do projeto (connections, schema PostgreSQL, pipeline de dados), templates para plugin + DAG + workflow + testes, e passo-a-passo de setup na infra.

Começamos a semana criando DAGs manualmente. Terminamos codificando o padrão para que DAGs futuras nasçam prontas 🚀.


🌐 O outro trilho: ActivityPub e o Fediverso

Em paralelo à decomposição do monolito, o activitypub-server (criado em 13/fev) ganhava sua peça final: a integração com o pipeline de dados. O servidor já existia, mas como ele saberia que existem notícias novas? 🤔

A resposta: uma DAG no Airflow. O PR central (activitypub-server#1 — Phase 6) adicionou coluna news_payload JSONB na fila de publicação e a DAG federation_publish que roda a cada 10 minutos. No infra, três PRs prepararam o terreno: remoção da variável PORT reservada (infra#79), fix de conectividade (infra#81) e secrets para o banco (infra#84).

Com a DAG em produção, veio o ciclo clássico de observar e otimizar 🔁. Em 24 horas, seis PRs de ajuste: batch loop (#2), max_active_runs=1 (#3), batch INSERT (#5), migração para reusable workflow (#6), ambiente local com Astro CLI (#7) e remoção do limite de fila (#8). A sequência batch loop → max_active_runs → batch INSERT é o tipo de história que nenhum plano prevê — e tá tudo bem 😄.

O Astro CLI (#7) merece nota: avança a issue data-platform#42 e cria o padrão de ambiente Airflow local que será replicado nos outros repos.


🎮 Organizando a sala de controle

Enquanto os repos de dados eram decompostos, o repo project ganhava forma como sala de controle do DGB. A skill /enviar-telegram (project#1) foi a primeira a chegar — permite enviar resumos de sprint, daily e backlog direto para o canal da equipe no Telegram 📲.

E no portal, a documentação dos feeds RSS/Atom/JSON (portal#80) finalizou uma peça que faltava para consumidores externos da API.


🔀 O antes e depois

Antes (20/fev)

block-beta
    columns 1
    block:monolito["📦 data-platform (monolito)"]
        columns 3
        scrapers["🕷️ scrapers/"]
        api["⚡ api.py"]
        dags["📋 dags/ (TODAS)"]
        managers["💾 managers/"]
        cogfy["🤖 cogfy/"]
        typesense["🔍 typesense/"]
    end

Depois (26/fev)

block-beta
    columns 3
    scraper["🕷️ scraper\nCloud Run API + DAGs"]:1
    publishing["📤 data-publishing\nSync PG → HuggingFace"]:1
    activitypub["🌐 activitypub-server\nFederação + DAG"]:1
    platform["⚙️ data-platform\nCore: Cogfy, Typesense"]:1
    workflows["♻️ reusable-workflows\nCI/CD compartilhado"]:1
    infra["🏗️ infra\nTerraform, IAM, WIF"]:1

Cada repo tem seu próprio CLAUDE.md, deploya suas DAGs num subdiretório do Composer, usa o reusable workflow e tem autonomia completa de release ✅.


✅ Issues resolvidas pelo caminho

Uma surpresa positiva da semana: ao revisar o backlog, descobrimos que várias issues mapeadas foram resolvidas como efeito colateral das refatorações — nenhuma atacada diretamente 🎯.

🟢 Fechadas

Issue Título Como foi resolvida
data-platform#57 Migrar Scraper para Airflow Implementação direta → extração para repo scraper
data-platform#28 Sync HF em ambiente isolado Extração para data-publishing com plugins (sem K8s!)
data-platform#22 DAG de exportação HuggingFace Refatorada e migrada para data-publishing
docs#30 Criar repo data-science Repo criado em 13/fev com 14 issues de pesquisa

🟡 Avançadas

Issue Título Progresso
data-platform#42 Astro CLI para dev local ~70% — padrão pronto no activitypub-server, falta replicar
data-platform#45 Remover código morto HF ~50% — falta limpar StorageAdapter
data-platform#73 Monitoramento do scraper ~30% — Airflow dá visibilidade por DAG, faltam alertas
docs#15 Documentar DAG sync HF ~40% — atualizar docs com nova arquitetura
data-platform#64 Campo active para agências Habilitada via pause/unpause de DAGs no Airflow

📊 Números

Métrica Valor
🔀 PRs mergeados 36
📁 Repos tocados 9
🆕 Repos novos 3 (scraper, data-publishing, data-science)
✅ Issues fechadas 4
🟡 Issues avançadas 5
📝 Planos escritos 4
🤖 Skills criadas 2 (/enviar-telegram, /criar-dag)
📅 Dias 6

💡 Lições

1. 📋 Planejar antes de codar. Cada refatoração teve um _plan/*.md com arquitetura alvo, sequência de migração e critérios de verificação. Nenhum plano sobreviveu intacto ao contato com produção, mas todos evitaram decisões erradas que custariam horas.

2. 🔄 Migrar sem downtime é uma sequência, não um evento. Criar novo → validar → pausar antigo → remover. Sempre nessa ordem. Nunca quebramos o pipeline em produção.

3. ♻️ Reusable workflows são multiplicadores. Um workflow parametrizado economiza meia hora de configuração em cada repo novo. Com três repos adotando em uma semana, já se pagou.

4. 🎯 Orquestrar, não executar. Mover execução pesada para Cloud Run e manter DAGs leves (duas linhas de HTTP) foi a decisão de arquitetura mais impactante. Nasceu da frustração com o Composer — às vezes a melhor ideia vem enquanto você espera ⏳.

5. 🔁 O ciclo deploy-observar-otimizar é inevitável. Os PRs #2 a #8 do activitypub-server são a prova: nenhum design prevê tudo. O importante é que o ciclo seja rápido.

6. 🤖 Codificar padrões, não só usar. Quando um padrão se repete três vezes, vale transformar em skill ou template. A /criar-dag nasceu depois de criarmos DAGs em três repos — agora o próximo dev (ou o Claude Code) replica o padrão sozinho.

7. 🏛️ Boa arquitetura resolve problemas que você nem estava olhando. Das quatro issues fechadas, nenhuma foi atacada diretamente. Todas caíram como efeito colateral de decisões arquiteturais. Isso diz algo sobre o valor de refatorar com intenção.


Todos os PRs, issues e planos estão linkados ao longo do texto. O código é aberto — explore à vontade 🙌