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:
Settingseload_settings()emsrc/core/config/settings.py..env.example, sem valores privados.template.yaml: parâmetro, variável em cada Lambda consumidora e IAM mínimo.scripts/deploy_from_env.ps1: leitura, validação, SSM e encaminhamento de parâmetros.- Perfis de
samconfig.toml, quando fizerem parte da decisão de ambiente. - 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/.