> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Arquitetura do Fetcher

> Como o Fetcher roda: um serviço Manager, um serviço Worker e o Engine de extração sem dependências que os dois hospedam no próprio processo.

O Fetcher é distribuído como dois serviços. O **Manager** expõe a API HTTP e despacha o trabalho. O **Worker** consome esse trabalho e produz resultados.

Os dois serviços são **hospedeiros**. Nenhum deles contém a lógica de extração. Eles rodam o mesmo **Engine** no próprio processo, pelas mesmas portas, sob as mesmas regras. Esse único fato explica o resto desta página, e explica por que a sua própria aplicação pode rodar a extração do Fetcher sem nenhum dos dois serviços.

## Os dois serviços

***

| Serviço     | Papel                                                                                                           |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| **Manager** | API HTTP para conexões e jobs. Mantém os metadados no MongoDB. Publica jobs de extração no RabbitMQ.            |
| **Worker**  | Consumidor de fila. Roda a extração, protege o resultado, o grava em object storage e emite um evento terminal. |

### Manager

O Manager segue um desenho hexagonal. Adaptadores HTTP mapeiam requisições para serviços, e os serviços alcançam a infraestrutura apenas por portas. Caminhos de leitura e caminhos de escrita ficam em pacotes de serviço separados.

Ele serve doze operações em dois prefixos de caminho:

| Caminho                                                     | Operações                                                                  |
| ----------------------------------------------------------- | -------------------------------------------------------------------------- |
| `/v1/fetcher`                                               | Criar um job de extração. Obter um job pelo identificador.                 |
| `/v1/management/connections`                                | Listar e criar conexões. Validar um mapeamento de esquema.                 |
| `/v1/management/connections/{id}`                           | Obter, atualizar, excluir, testar e descobrir o esquema de uma conexão.    |
| `/v1/management/connections/unassigned` e `.../{id}/assign` | Duas operações de migração para conexões anteriores ao escopo por produto. |

Três dependências de infraestrutura sustentam a API:

* **MongoDB** guarda os registros de conexão e os registros de job.
* **RabbitMQ** leva um novo job até o Worker na fila que `RABBITMQ_FETCHER_WORK_QUEUE` nomeia.
* **Valkey ou Redis** sustenta o cache de esquema e o limitador de taxa do teste de conexão. O cache de esquema cai para a memória do processo quando o Redis está inacessível.

Uma requisição de criação de job responde `202 Accepted`. Uma repetição da mesma requisição dentro de uma janela de cinco minutos responde `200 OK` com o job que já existe.

### Worker

O Worker não tem servidor HTTP principal. Ele consome a fila de trabalho e roda `RABBITMQ_NUMBERS_OF_WORKERS` jobs em paralelo, cinco por padrão. Um pequeno servidor de saúde em `HEALTH_PORT` carrega as sondas e o endpoint de métricas.

Para cada job, o Worker:

1. Resolve as conexões que o job mapeia e roda a extração pelo Engine embarcado.
2. Assina o JSON em texto claro com HMAC-SHA256, usando uma chave derivada da chave-mestra.
3. Criptografa o payload assinado com AES-GCM e o grava em object storage compatível com S3.
4. Publica `job.completed` ou `job.failed` no exchange que `RABBITMQ_JOB_EVENTS_EXCHANGE` nomeia.

O Worker conduz o Engine em modo direct. O Engine devolve os bytes exatos, e o Worker é dono da proteção e do armazenamento desses bytes. Os dois eventos terminais são rotas obrigatórias. O Worker se recusa a iniciar quando `STREAMING_ENABLED` é falso ou o exchange de eventos está em branco, porque um emissor silencioso engoliria o contrato.

<Note>
  O Worker fala o protocolo S3. O SeaweedFS funciona como destino de armazenamento pelo gateway compatível com S3 dele, que é como a stack local do Docker Compose roda. Defina a retenção dos resultados com uma política de ciclo de vida no bucket.
</Note>

### Mensagens entre eles

Toda mensagem que o Manager publica carrega uma assinatura HMAC-SHA256. O payload assinado amarra o timestamp, a versão da assinatura, o ID do tenant, o ID do job, o exchange e a routing key. O corpo vem por último. Um replay do mesmo corpo sob outro tenant ou outra rota falha na verificação. O assinador recusa qualquer chave menor que 32 bytes e compara assinaturas em tempo constante.

## O Engine por baixo

***

O Engine é dono do que uma extração **significa**. Um hospedeiro é dono de como ela **roda**.

| O Engine decide                      | O hospedeiro fornece                                   |
| ------------------------------------ | ------------------------------------------------------ |
| Regras do ciclo de vida da conexão   | Onde as conexões ficam                                 |
| Descoberta e validação de esquema    | O driver que lê o catálogo                             |
| Planejamento e execução de consultas | Os conectores de banco de dados                        |
| Limites de recursos e tempos limite  | O transporte que os expõe                              |
| Escopo de tenant em toda operação    | A identidade do tenant vinda da requisição             |
| Categorias de erro                   | O status HTTP ou o comportamento de fila por categoria |

O Engine é um módulo Go separado: `github.com/LerianStudio/fetcher/pkg/engine`. O `go.mod` dele não tem bloco `require` algum, e um job de CI falha o build se um aparecer. Um segundo teste imposto no build percorre toda dependência transitiva e rejeita qualquer coisa que não seja nem a biblioteca padrão do Go nem local ao módulo do Engine. Nenhum framework HTTP, nenhum cliente de fila, nenhum driver de banco de dados e nenhum SDK de nuvem consegue entrar.

Essa restrição é o ponto. O Engine não acrescenta nenhuma dependência de terceiros, então uma aplicação hospedeira pode incorporá-lo sem uma única entrada nova na própria árvore de dependências.

### Portas

Um hospedeiro liga o Engine por um conjunto pequeno de portas. Uma é sempre obrigatória. Uma segunda é obrigatória apenas quando a persistência criptografada está ligada. As demais são opcionais.

| Porta                    | Obrigatória | Sem ela                                                                                                                                        |
| ------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `ConnectorRegistry`      | Sim         | O Engine se recusa a construir.                                                                                                                |
| `CredentialProtector`    | Condicional | Obrigatória quando a persistência criptografada está ligada. O Engine se recusa a construir em vez de armazenar uma credencial em texto claro. |
| `ConnectionStore`        | Não         | O Engine constrói, mas toda operação que toca em uma conexão falha. Na prática, ele só consegue reportar os próprios limites.                  |
| `SchemaCache`            | Não         | Toda chamada de esquema descobre ao vivo na fonte de dados.                                                                                    |
| `ResultSink`             | Não         | O modo store fica indisponível. A extração retorna as linhas inline.                                                                           |
| `ExecutionStore`         | Não         | O Engine não rastreia estado durável de execução. O hospedeiro é dono do status.                                                               |
| `ActiveExecutionChecker` | Não         | O Engine não aplica trava de conflito. Atualizações e exclusões de conexão seguem em frente.                                                   |
| `Observability`          | Não         | Os hooks de span desaparecem. O comportamento não muda.                                                                                        |

Uma porta fornecida como nil tipado conta como ausente. O Engine detecta isso na construção, então um hospedeiro mal configurado falha na inicialização em vez de entrar em pânico na primeira chamada.

O módulo também traz implementações em memória das portas de armazenamento — o registro de conectores, o connection store, o cache de esquemas, o result sink e o execution store. Isso cobre a infraestrutura que um teste precisa, então você pode exercitar o Engine sem MongoDB, sem Redis, sem RabbitMQ e sem object storage. Ele não traz um `CredentialProtector`, então um teste que ligue a persistência criptografada precisa fornecer o seu.

### Classificação de falhas

O Engine retorna uma de onze categorias estáveis: `validation`, `not_found`, `unauthorized`, `forbidden`, `limit_exceeded`, `conflict`, `unavailable`, `connect`, `timeout`, `canceled` e `internal`. Um hospedeiro mapeia cada categoria para o próprio transporte. O Manager mapeia `conflict` para HTTP 409, por exemplo.

Erros que cruzam a fronteira do Engine carregam texto fixo. O Engine descarta o erro de driver por trás de uma falha, porque esse texto pode embutir uma DSN, uma credencial ou detalhes internos do driver.

## Saúde e prontidão

***

Os dois serviços montam `/health`, `/readyz`, `/readyz/tenant/{id}` e `/metrics` antes da autenticação, então o Kubernetes e os balanceadores de carga os alcançam sem token.

* `/health` responde 503 até a autossondagem de inicialização ter sucesso. O kubelet então reinicia um pod que não conseguiu alcançar as dependências no boot.
* `/readyz` roda toda sonda de dependência em paralelo a cada requisição, sem cache e com um tempo limite por dependência. O Manager sonda MongoDB, RabbitMQ e Redis. O Worker sonda MongoDB, RabbitMQ e o bucket de armazenamento. Em modo multi-tenant, os dois serviços somam o Tenant Manager e o Redis multi-tenant.
* Em `SIGTERM`, os dois serviços respondem `/readyz` com 503 por `READYZ_DRAIN_DELAY_SEC` segundos, 12 por padrão, antes de derrubar as conexões. O Kubernetes remove o pod do Service enquanto ele ainda atende tráfego.

## Incorporar em vez de implantar

***

O Manager e o Worker são hospedeiros, não camadas privilegiadas. Forneça um registro de conectores e um armazenamento de conexões, e a sua aplicação ganha o mesmo planejamento de consultas, os mesmos limites, as mesmas checagens de tenant e o mesmo formato de resultado no próprio processo. Ela não precisa de fila, de object storage nem de uma implantação separada.

Os produtos Lerian incorporam o Engine exatamente assim. Leia [Conceitos centrais](/pt/fetcher/fetcher-core-concepts) para o modelo que o Engine implementa.

## Próximos passos

***

<CardGroup cols={2}>
  <Card title="Conexões" icon="plug" href="/pt/fetcher/fetcher-connections">
    Registre, teste, atualize e exclua uma conexão com uma fonte de dados.
  </Card>

  <Card title="Configuração" icon="gear" href="/pt/fetcher/fetcher-configuration">
    As variáveis de ambiente que moldam uma implantação do Fetcher.
  </Card>
</CardGroup>
