O Engine é um módulo Go distinto dos serviços do Fetcher. O caminho do módulo é
github.com/LerianStudio/fetcher/pkg/engine, e o módulo dos serviços é github.com/LerianStudio/fetcher/v2. O Engine tem sua própria linha de versões, com tags prefixadas pelo caminho (pkg/engine/vX.Y.Z). Uma importação do Engine não herda nenhuma dependência dos serviços.O Fetcher é source-available sob a Elastic License 2.0, e o Engine carrega a mesma licença. Você lê as regras de extração que incorpora.O modelo de três camadas
O princípio que guia o desenho: o Engine é dono do que faz o Fetcher ser Fetcher, e as aplicações hospedeiras são donas de como o Fetcher roda.
Todo produto que incorpora o Engine compartilha, portanto, um único dono canônico do comportamento de fonte de dados e de extração. Uma mudança de regra no núcleo chega a todos os hospedeiros na próxima atualização do módulo, e nenhum hospedeiro a reimplementa.
A fronteira de importação
O módulo do Engine declara zero dependências de terceiros. O
go.mod dele não tem bloco require algum, e um teste imposto no build mantém as coisas assim.
Duas proteções rodam dentro do módulo a cada go test ./..., e o workflow de CI do módulo as executa com o workspace do Go desligado:
- A lista de permissão. Toda dependência transitiva de
pkg/engineprecisa ser um pacote da biblioteca padrão do Go ou um pacote local ao módulo do Engine. Qualquer outra família de importação quebra o build, mesmo uma família que nenhuma lista de bloqueio cita. - Uma lista de bloqueio nomeada. Classes enumeradas falham em cima da lista de permissão. Elas cobrem frameworks HTTP, message brokers, drivers de banco de dados, SDKs de object storage, middleware de runtime de tenant, as bibliotecas de autenticação e de licença, e invólucros da biblioteca padrão como
database/sql,net/httpeos/exec.
require no go.mod do Engine e falha o job quando encontra uma.
Por que isso importa quando você incorpora
- Sem conflitos de dependência. O Engine não consegue puxar um driver, um cliente de broker ou um framework HTTP para o grafo do seu módulo. O seu hospedeiro mantém controle total das próprias versões.
- Sem I/O oculto. O núcleo não consegue abrir um socket, um arquivo ou um banco de dados por conta própria. Cada byte que entra e sai atravessa uma porta que você forneceu.
- Um ponto de substituição estável. Rode o Engine inteiro contra o harness em memória nos testes. Troque pelos adaptadores reais em produção, sem mudar o código que chama.
Modo Direct e modo Store
Um plano de extração carrega um modo com três valores:
direct, store e o valor zero auto.
O Engine resolve auto a partir das portas que você ligou. Com um ResultSink configurado, ele escolhe o modo store; sem um, escolhe o modo direct. Ou seja, o modo é consequência da sua composição, não uma chave separada. Um pedido explícito de store sem sink falha logo no início, como erro de validação, antes de o Engine tocar em qualquer fonte de dados.
Os dois modos retornam formatos diferentes. Exatamente um braço do resultado é não nulo, e o JSON omite o braço não usado por completo.
Modo Direct
O Engine executa os passos do plano e junta as linhas em um único mapa. As chaves do mapa são o nome da configuração da fonte de dados e, em seguida, a tabela qualificada. O Engine serializa esse mapa uma vez como JSON indentado e retorna os bytes inline.
A saída do modo direct é determinística. O serializador ordena as chaves do mapa. A mesma entrada, portanto, dá a você um JSON idêntico byte a byte e o mesmo digest, qualquer que tenha sido a ordem em que os passos paralelos terminaram.
O seu hospedeiro é dono de tudo depois disso. O Fetcher Worker, por exemplo, assina o texto claro com HMAC-SHA256 e o criptografa antes de armazenar os bytes.
Modo Store
O Engine abre um stream no seuResultSink e escreve o resultado de forma incremental, com memória constante. Ele nunca segura o resultado inteiro. Um único escritor drena as goroutines de extração estritamente na ordem crescente dos passos do plano, e o digest de integridade cobre exatamente os bytes escritos.
O formato de saída é NDJSON — um objeto JSON por linha, terminado por quebra de linha, sem array envolvente:
Em um aborto — um erro de escrita, um limite de tamanho excedido ou um contexto cancelado — o Engine abandona o escritor e nunca chama
Close. Um resultado parcial, portanto, nunca vira uma referência retornada. Trate um escritor não fechado como uma escrita descartada.
Contratos de falha e de resultado
- Falha rápida entre fontes de dados. O primeiro passo que falha interrompe a execução. O Engine nunca retorna um resultado parcial.
- Erros redigidos. Um erro de driver pode embutir uma DSN, uma credencial ou detalhes internos do driver, então o Engine o descarta e retorna uma mensagem fixa no lugar.
- Onze categorias de erro.
validation,not_found,unauthorized,forbidden,limit_exceeded,conflict,unavailable,connect,timeout,canceledeinternal. O seu hospedeiro as mapeia para os próprios códigos de transporte.connectpermanece distinto deunavailable, etimeoutpermanece distinto decanceled. - Cinco status de execução.
pending,running,completed,failedecanceled. Os três últimos são terminais. Um cancelamento pelo hospedeiro registracanceled, e um prazo estourado registrafailed. - Conectores fechados. Todo conector que o Engine abre é fechado de volta, no caminho de sucesso e em todos os caminhos de falha.
Limites e escopo de tenant
O Engine nunca roda sem limite. Quando você não fornece limites, ele aplica estes padrões:
Uma requisição pode reduzir qualquer limite e nunca aumentá-lo. Um override acima do padrão falha com um erro de validação que nomeia o campo violado. Um override zero ou negativo mantém o padrão.
O Engine impõe o teto de tamanho do resultado duas vezes: um limite inferior barato por passo, que aborta cedo, e uma checagem definitiva sobre o payload indentado final. Um resultado acima do limite nunca chega a você inline e nunca chega ao seu sink.
O escopo de tenant é igualmente estreito. Toda operação carrega um ID de tenant e nada além disso — o Engine não tem conceito de organização nem de produto. Ele valida o ID de tenant antes de qualquer acesso a recurso, e rejeita ali um valor vazio ou malformado.
Próximos passos
Incorporar o Engine
Importe, forneça as portas e construa com um exemplo executável.
Referência de portas
Cada porta, se ela é obrigatória e o que acontece sem ela.

