
Construímos o Wayler, um orquestrador de ETL, simples e determinístico, em vez de recorrer aos gigantes da indústria. Um caso de uso de Python procedimental, das fronteiras do Docker e de processos que fazem exatamente o mesmo, sempre.
Índice
Se passarmos cinco minutos nas redes sociais onde se fala sobre tecnologia, ficamos com a ideia de que a única forma de construir software hoje em dia é lançar um conjunto de agentes de IA autónomos. Não nos interpretem mal — na Waymotion não somos anti-IA. Na verdade, usamos ferramentas de IA todos os dias para escrever código, desenhar sistemas e resolver problemas complexos.
Mas recentemente, perante uma tarefa de migrar dados geoespaciais complexos de serviços ArcGIS REST antigos e de APIs internas para uma OGC API normalizada (ver este caso), decidimos ir contra a corrente: construímos o nosso próprio orquestrador de ETL, simples e determinístico (como antigamente).
Chamámos-lhe Wayler (como usámos docker no core, o nome foi a parte fácil). Eis porque o construímos, porque não optámos pelos gigantes da indústria e como apostar em Docker e em Python procedimental nos deu exatamente o controlo e a sustentabilidade de software de que achámos ser suficiente.
O «problema» das ferramentas de dados modernas
Quando tudo o que precisamos de fazer é recolher dados de uma API, transformá-los para uma norma específica (como o INSPIRE) e carregá-los com segurança para uma base de dados relacional, o ecossistema atual apresenta uma dicotomia frustrante, que é crucial ponderar quando, especialmente numa equipa pequena:
- Os pesos-pesados (Spark, Airflow, Mage.ai): são ferramentas notáveis para data lakes massivos e distribuídos. Mas configurar um cluster ou escrever abstrações complexas de DAG só para paginar um endpoint ArcGIS REST é como usar uma martelo para partir uma noz.
- Os construtores visuais (n8n, Zapier): excelentes para webhooks simples, mas transformam-se facilmente em esparguete visual assim que é preciso lidar com transformações geoespaciais complexas ou com conversões de coordenadas (EPSG:3763 para EPSG:4326).
Percebemos que não queríamos abstrair o código, porque queríamos o controlo de escrever Python procedimental. Se for preciso um IF baseado numa escala de 1:50k contra 1:200k, queremos vê-lo explicitamente num ficheiro .py.
Quando processos e builds determinísticos são possíveis, na nossa visão são muitíssimo superiores para a sustentabilidade do software a longo prazo. Não temos de nos interrogar sobre porque é que um LLM alucinou um mapeamento de esquema, nem de desemaranhar um grafo de nós gigantesco. Simplesmente funciona (ou não), exatamente da mesma maneira, sempre.
Além disso, não queríamos depender do sistema operativo da máquina anfitriã nem de SaaS de terceiros para o agendamento. Precisávamos de um agendador nosso, a correr exclusivamente na nossa infraestrutura, totalmente isolado e seguro.
O Wayler e os seus Harpoons
Para resolver isto, desenhámos o Wayler — um orquestrador leve e feito à medida, assente em Docker.
Na nossa arquitetura, o Wayler funciona como centro de comando. Não processa dados; apoia-se numa base de dados local para gerir agendamentos, novas tentativas e alertas. Quando chega a hora agendada, lança um Harpoon — um script Python em contentor, de propósito único.
A decisão Docker: uma funcionalidade, não um defeito
Construir um orquestrador assim traz uma ressalva importante: depende 100% do Docker. O Wayler tem de ter acesso ao socket do Docker para criar contentores, monitorizar os respetivos códigos de saída e destruí-los. Mas, para nós, esta dependência foi deliberada, e acreditamos que foi uma decisão altamente vantajosa.
Ao obrigar cada trabalho de ETL a viver num contentor Docker, garantimos que cada script Python vive no seu próprio mundo. Não há «inferno de dependências» em que uma atualização do GeoPandas para um script estrague outro. Também torna o lançamento de novas versões incrivelmente simples — construímos e etiquetamos uma nova imagem do Harpoon, atualizamos o registo na base de dados, e a execução agendada seguinte usa o novo código sem afetar o resto da frota.
Além disso, isto permitiu-nos impor um comportamento estrito ao SDK (internamente chamamos-lhe «Iron»). Cada Harpoon herda de uma classe Python BaseHarpoon. Os programadores implementam apenas um método process(), e o SDK trata automaticamente do registo consistente, das sessões de base de dados, da captura de erros e de uma flag —dry-run obrigatória.
Veja como é simples implementar um Harpoon com o SDK:
from wayler_sdk import BaseHarpoon
from integrations.arcgis import ArcGISClient
class MineralOccurrencesHarpoon(BaseHarpoon):
def process(self):
self.logger.info("Starting Voyage: Fetching ArcGIS data...")
# 1. Fetch data from source
client = ArcGISClient(url=self.config.SOURCE_URL)
raw_data = client.fetch_all_features()
# 2. Procedural, deterministic transformation
self.logger.info(f"Retrieved {len(raw_data)} records. Transforming...")
clean_data = self.transform_to_inspire(raw_data)
# 3. The SDK handles the atomic swap to the DB
self.logger.info("Loading into PostGIS Staging...")
self.atomic_load(
target_table="stg_inspire_ge_minocc",
data=clean_data
)
self.logger.info("Voyage successful!")
if __name__ == "__main__":
# The run() method handles dry-runs, retries, and DB sessions
MineralOccurrencesHarpoon().run()Entrega de dados sem interrupção (a troca atómica)
O principal consumidor destes dados é um serviço pygeoapi público que lê de uma base de dados PostGIS. Se um Harpoon demora 20 minutos a descarregar e processar milhares de elementos geológicos, a API pública não pode servir dados vazios ou parciais durante essa janela.
Como temos controlo total sobre o Python procedimental, o nosso SDK impõe um padrão de troca atómica:
Para onde vamos a partir daqui (um roteiro possível)
O Wayler revelou-se tão estável que já pensamos alargá-lo a outros processos internos de sincronização, para lá dos dados geoespaciais. Ainda assim, qualquer equipa de engenharia honesta sabe que o seu sistema ainda não é perfeito (e nunca será :D). Temos dois pontos maiores no roteiro de dívida técnica:
- Uma interface própria: neste momento, gerir o Wayler significa correr alguns comandos docker e escrever INSERTs de SQL clínicos na base de dados do orquestrador. Precisamos de construir um painel limpo e bonito para visualizar pipelines, despoletar execuções manuais de Harpoons e acompanhar os registos.
- Gestão de segredos: a nossa próxima grande evolução arquitetónica é integrar um gestor de segredos dedicado, para que os Harpoons passem a ir buscar as suas credenciais dinamicamente em tempo de execução (o mage.ai, por exemplo, faz isto muito bem).
Conclusão
Numa era em que a indústria tecnológica corre para entregar todos os fluxos de trabalho a um LLM, vale a pena lembrar que a sincronização de dados é muitas vezes um prato que se serve frio e determinístico. Adoramos a IA naquilo em que ela é melhor. Mas para migrar dados? Não me parece que precisemos dela. Abraçámos a sustentabilidade do Python procedimental, as fronteiras estritas do Docker e a fiabilidade de um agendador feito por nós.
Como programador que tenta acompanhar os superpoderes dos LLM, admito que houve uma pequena parte de mim que temeu, ao início, que a «diversão» da IA se perdesse ao escolher a via tradicional e determinística em vez de um enxame de agentes autónomos. Mas a verdade é que os Harpoons continuam a ter de ser desenvolvidos. A arquitetura continua a ter de ser desenhada, as APIs continuam a ter de ser sujeitas a engenharia inversa, e a IA está mesmo ali, nos nossos IDE, a ajudar-nos a escrever esse Python procedimental mais depressa e melhor do que nunca. Ficamos com a fiabilidade do código determinístico, sem perder a alegria do «desenvolvimento moderno».
O que me deixa a pensar: por vezes, a coisa mais inovadora que se pode fazer é escrever código que faz exatamente aquilo que lhe mandamos fazer, mesmo que seja um bug.



