Pular para conteúdo

Adicionar ou substituir uma integração

O padrão real

O core recebe um bundle Dependencies de src/core/integrations/factory.py. Ele chama métodos definidos por Protocol, e o factory escolhe a implementação conforme Settings. Stubs implementam a mesma assinatura e retornam o mesmo tipo dos clientes reais.

Use a integração eGestor como exemplo pequeno: EGestorClient.upsert_customer(payload) retorna StubResponse. O cliente HTTP implementa autenticação, busca e upsert; o caso de uso de confirmação decide o que fazer quando a resposta falha. eGestor documenta o contrato integral.

1. Delimitar o contrato

Antes de escrever HTTP, identifique o consumidor e seu contrato de domínio. A operação é uma consulta, uma escrita idempotente ou uma criação cuja resposta pode se perder? Que ID o consumidor precisa armazenar? Uma falha interrompe o turno ou pode ser reconciliada por job?

O contrato existente abaixo é uma boa referência estrutural; não é necessário criar uma cópia se você só substituir o provider atual.

# src/core/integrations/egestor/interface.py
class EGestorClient(Protocol):
    def upsert_customer(self, payload: dict[str, Any]) -> StubResponse: ...

O retorno em src/core/integrations/types.py possui success, mock_id, detail e error_kind (transient, permanent, uncertain ou None). Apesar do prefixo mock, o cliente real usa mock_id como ID do recurso remoto. Preservar esse formato mantém os consumidores compatíveis.

2. Implementar o adapter e o payload builder

Para trocar eGestor por uma API compatível, preserve EGestorClient e o formato interno. Traduza o payload na borda, como src/core/integrations/egestor/payloads.py faz hoje. Não espalhe nomes de campos do provider pelo orquestrador.

Separe uma configuração imutável com URL, timeout e política de retry. Resolva autenticação sem imprimir credenciais. Estabeleça validações para IDs ausentes, JSON inesperado, lista vazia, paginação inconsistente e colisão de identidades.

Situação Decisão necessária
GET falha antes de obter resposta Definir retry limitado para transporte/5xx/429
POST enviado sem resposta conclusiva Classificar incerteza; consultar/reconciliar antes de recriar
PATCH retorna 404 Definir bloqueio explícito; não inferir que pode criar outro recurso
Contato encontrado por chave fraca Confirmar documento/identidade antes de atualizar
Sucesso sem ID Tratar como erro de contrato, não como confirmação de cadastro

O CRM assíncrono distingue BLOCKED e UNCERTAIN justamente para evitar repetição cega de escritas. Ao mudar provider, preserve essas garantias em CRM.

3. Criar o stub

Um stub deve ser determinístico, não usar rede e produzir resposta compatível. O eGestor atual retorna StubResponse(success=True, mock_id='mock_egestor_cliente_789'). Para testes de falha, configure doubles que retornem os mesmos tipos de erro que o cliente real.

Não use um stub que retorne sucesso sem os campos exigidos pelo caso de uso. Isso esconderia diferenças da implementação real. Também não use telefones, documentos ou e-mails de clientes em payloads de teste.

4. Compor no factory

Ao substituir um provider atrás do mesmo Protocol, altere a factory correspondente e mantenha o tipo do atributo em Dependencies. Para uma integração inédita, proponha explicitamente um novo atributo no bundle e adapte seus construtores de teste.

No padrão eGestor, build_egestor_client(settings) retorna o stub quando a flag está desligada; quando ligada, _resolve_setting_or_ssm() resolve URL/token antes de construir EGestorHttpClient. A função aceita valor direto primeiro e SSM como fallback. O chamador não precisa saber como a credencial foi obtida.

5. Propagar configuração

Uma nova variável pode precisar de alterações coordenadas em:

  1. Settings e load_settings() em src/core/config/settings.py.
  2. .env.example, sem valores privados.
  3. template.yaml: parâmetro, variável em cada Lambda consumidora e IAM mínimo.
  4. scripts/deploy_from_env.ps1: leitura, validação, SSM e encaminhamento de parâmetros.
  5. Perfis de samconfig.toml, quando fizerem parte da decisão de ambiente.
  6. Referência de variáveis e página da integração.

Não presuma que uma variável adicionada a .env.example chega ao SAM. tests/unit/scripts/test_deploy_from_env.py e tests/unit/test_template_scoping.py validam partes dessa propagação. Credenciais devem chegar somente às funções consumidoras.

6. Testar em camadas

python -m pytest tests/unit/integrations/test_factory.py -q
python -m pytest tests/unit/integrations/test_egestor_http_client.py -q
python -m pytest tests/unit/application/test_confirm_action.py -q
python -m pytest tests/unit/scripts/test_deploy_from_env.py tests/unit/test_template_scoping.py -q

Adicione casos equivalentes para o novo provider: sucesso, autenticação, timeout, erro HTTP, JSON inválido, resposta parcial, retry esgotado e escrita incerta. Verifique também o consumidor: qual estado é persistido, quais IDs sobrevivem e se o cliente recebe handoff quando necessário.

Use mocks de HTTP e moto para os testes automatizados. Um teste que só verifica se o método da própria implementação foi chamado não comprova o contrato com o caso de uso.

7. Acrescentar smoke controlado

O padrão está em src/core/application/smokes/: models, runners específicos e runner.py. O CLI scripts/smoke.py e a UI do dashboard são consumidores. Um novo target precisa de preflight, sanitização e flags de escrita/envio compatíveis com esse mecanismo.

Especifique previamente o que é leitura e o que causa efeito remoto. Uma chamada chamada “health” ainda pode usar rede e credenciais. Execute o smoke real somente em homologação autorizada, com dados fictícios e plano de cleanup. O guia de smokes descreve as flags existentes.

8. Planejar migração e rollback

Trocar apenas a URL é suficiente somente quando auth, endpoints, payloads, códigos HTTP e identidade forem compatíveis. Preserve IDs remotos existentes ou registre um mapa de migração; um ID do provider antigo não pode ser reutilizado sem validação no novo.

Registre configurações anteriores sem expor segredos, critérios de aceite e como reconciliar operações pendentes. Rollback do código não desfaz um contato criado, um e-mail enviado ou um deal remoto. Na confirmação atual, CRM e eGestor não participam de uma transação distribuída.

Fontes: src/core/integrations/factory.py, src/core/integrations/types.py, interfaces/clientes/stubs em src/core/integrations/, src/core/application/confirm_action.py, src/core/application/crm_sync.py, tests/unit/integrations/.