Pular para conteúdo

ADR-008 — amIA-Integration: Gateway dedicado para integração de chamados externos (ServiceNow)

Status: ✅ Aceito Data: 2026-07-01

Contexto

O módulo AMS precisa receber chamados de clientes originados em sistemas externos de ticketing, começando pelo ServiceNow, que enviará dados via webhook customizado a cada criação/atualização de chamado. Esse é o primeiro de potencialmente vários conectores futuros (Jira Service Management, Zendesk, etc. — ver amIA-Marketplace).

O fluxo de recepção externa envolve responsabilidades distintas das que o amIA Core hoje concentra:

  1. Recepção de dados externos — endpoint público de webhook, validação de assinatura/token do ServiceNow, rate limiting e proteção contra payloads maliciosos ou duplicados.
  2. Auditoria de integração — registro de todo payload recebido (raw), tentativas de reprocessamento, falhas de validação e status de entrega para troubleshooting com o cliente/fornecedor.
  3. Transformação/tratamento — mapear o schema proprietário do ServiceNow (campos, enums, anexos) para o modelo de domínio interno (WorkItem/chamado) do amIA, com regras de mapeamento que variam por sistema de origem.

O amIA Core hoje é responsável pela orquestração de negócio (projetos, specs, Work Items, iterations), RBAC, auditoria de domínio e API consumida pelo Portal e pelos Agents — via Clean Architecture + CQRS em .NET. A questão arquitetural é: adicionar os endpoints de webhook e a lógica de transformação como um novo módulo dentro do amIA Core, ou criar um serviço dedicado de gateway de integração?

Alternativas avaliadas

# Opção Descrição
A Incorporar no amIA Core Adicionar controllers de webhook, tabelas de auditoria de integração e mappers de transformação como novos módulos dentro do repositório e container do amIA Core.
B Criar amIA-Integration (escolhida) Novo repositório, imagem Docker e serviço dedicado exclusivamente à recepção de webhooks externos, auditoria de integração e transformação para o modelo de domínio interno, publicando eventos/comandos consumidos pelo amIA Core.
C Incorporar no amIA-Ingest Reaproveitar o serviço de ingestão já existente, tratando o webhook como mais uma fonte de dados a processar.

Decisão

Criar o serviço amIA-Integration como um gateway independente do amIA Core, responsável por expor os endpoints de recepção externa (iniciando pelo webhook do ServiceNow), auditar cada evento de integração e transformar o payload de origem no contrato de domínio consumido pelo amIA Core.

Justificativa

Isolamento de responsabilidades (SRP em nível de serviço)

O amIA Core é um serviço de orquestração de negócio: mantém o estado autoritativo de projetos, specs e Work Items e aplica regras de RBAC. A recepção de webhooks é um problema de borda de sistema: precisa validar payloads não confiáveis, lidar com formatos e taxas de entrega variáveis por fornecedor, e sobreviver a instabilidade do lado externo (retries, payloads fora de ordem, duplicidade). São ciclos de vida e superfícies de risco diferentes — expor o Core diretamente à internet como receptor de webhooks aumenta sua superfície de ataque sem necessidade.

Auditoria e troubleshooting isolados

Chamados vindos de sistemas de terceiros frequentemente geram disputas sobre "o que foi enviado" e "o que foi recebido". Um serviço dedicado permite reter o payload raw de cada requisição, versionar o schema de mapeamento e reprocessar eventos falhos sem competir com — ou poluir — a auditoria de domínio do amIA Core (que registra ações de usuários e RBAC, não tráfego de rede de terceiros).

Escalabilidade e resiliência independentes

Webhooks externos podem chegar em rajada (ex.: sincronização em massa do ServiceNow) e sistemas de terceiros costumam impor SLAs curtos de resposta (200 OK em poucos segundos). Um serviço dedicado pode escalar horizontalmente e usar fila (RabbitMQ) para amortecer picos, sem competir por recursos com o tráfego de negócio do Core nem arriscar timeout do webhook por lentidão em outra parte do sistema.

Extensibilidade para múltiplos conectores

ServiceNow é o primeiro de uma família de integrações (Jira, Zendesk, e-mail, etc.). Concentrar a lógica de "receber → validar → transformar → publicar" em um serviço próprio cria um ponto único de extensão: cada novo conector adiciona um adaptador de transformação, sem tocar no domínio ou na API do amIA Core. Embutir isso no Core acopla o crescimento do catálogo de integrações ao ciclo de release do serviço de negócio principal.

Deploy e superfície de exposição independentes

O endpoint de webhook precisa ser público (ou exposto via IP allowlist do ServiceNow), enquanto o amIA Core hoje é consumido primariamente pelo Portal e pelos Agents. Separar os serviços evita expandir a superfície de rede exposta do Core e permite políticas de rede, WAF e autenticação (token de conector por cliente) específicas para tráfego de integração, sem impactar os demais consumidores da API.

Por que não incorporar no amIA-Ingest

O amIA-Ingest (ver ADR-005) foi desenhado para o pipeline de ingestão de documentos e busca vetorial — extração de texto, chunking, embeddings e pgvector. Chamados de clientes não são conteúdo a ser vetorizado; são dados estruturados de negócio que precisam virar WorkItem/chamado no domínio do amIA Core. Forçar esse fluxo pelo amIA-Ingest misturaria dois domínios de dados (documentos semânticos vs. registros transacionais) e exigiria dependências (parsers de webhook, mapeamento de schema por fornecedor) irrelevantes para o propósito original do serviço.

Por que não incorporar no amIA Core

  • Exporia o Core diretamente a tráfego não confiável de terceiros, aumentando a superfície de ataque de um serviço que já concentra dados sensíveis e RBAC.
  • Acoplaria o roadmap de novos conectores (Jira, Zendesk, etc.) ao ciclo de deploy do serviço de negócio principal.
  • Misturaria auditoria de domínio (ações de usuário) com auditoria de integração (tráfego externo, payloads raw), dificultando queries e retenção de dados com propósitos distintos.
  • Picos de tráfego de webhook (ex.: sincronização em massa) poderiam degradar a API consumida pelo Portal e pelos Agents.

Decisões técnicas derivadas

Três decisões de design, definidas em detalhe no HLD do amIA-Integration, são registradas aqui por afetarem diretamente o contrato entre os dois serviços e a superfície de segurança do gateway — não são apenas detalhe de implementação.

Autenticação do webhook: token estático, não HMAC

A recepção usa header Authorization: Bearer <token>, validado por comparação de hash (SHA-256), em vez de assinatura HMAC do corpo da requisição. Motivo: o ServiceNow configura webhooks customizados via Outbound REST Message/Flow Designer, onde adicionar um header fixo é nativo; calcular HMAC exigiria scripting adicional do lado do cliente sem ganho de segurança relevante sobre um canal HTTPS. O token em texto puro só existe no Azure Key Vault (gerado no provisionamento) e na configuração do cliente externo — o amIA-Integration guarda e compara apenas o hash, nunca o valor reversível.

Propriedade da configuração de conectores: amIA-Core, não amIA-Integration

A configuração de "qual Client usa qual conector, com qual credencial" (ClientConnector) é dado de tenant, não dado operacional do gateway — por isso vive no amIA-Core (schema core, junto de Client), administrada pelo Portal com o mesmo RBAC já usado para os demais dados de cliente. O amIA-Integration não persiste essa configuração; mantém apenas um cache em memória, hidratado no startup a partir de um endpoint interno do Core e atualizado por evento, para validar tokens sem chamada síncrona ao Core a cada webhook recebido. Se a hydration inicial falhar, o serviço não aceita tráfego (fail-closed) — nunca opera com cache vazio.

Mensageria: fila de trabalho para chamados, broadcast para configuração

Dois padrões distintos de RabbitMQ, conforme onde o estado sincronizado vive: - CreateOrUpdateTicketCommand (Integration → Core): fila de trabalho comum (competing consumers) — o resultado fica no Postgres compartilhado do Core, não importa qual instância processa. - ClientConnectorUpserted/ClientConnectorDeactivated (Core → Integration): exchange fanout, com cada instância do Integration declarando sua própria fila exclusiva/auto-delete no startup — necessário porque o dado sincronizado (cache de credenciais) vive na memória de cada processo; uma fila de trabalho comum atualizaria só uma instância, deixando as demais com credenciais desatualizadas.

Consequências

Positivas: - Cada serviço mantém uma única responsabilidade, seguindo o mesmo padrão já validado em ADR-005 - Superfície de exposição a tráfego externo isolada do Core, reduzindo risco de segurança - Auditoria de integração (payload raw, reprocessamento, falhas de mapeamento) com modelo de dados e retenção próprios - Novos conectores (Jira, Zendesk, etc.) são adicionados como novos adaptadores no gateway, sem alterar o amIA Core - Picos de recepção de webhook não competem por recursos com a API de negócio

Negativas: - Mais um repositório, pipeline CI/CD e serviço a provisionar e operar - Necessidade de autenticação service-to-service entre amIA-Integration e amIA Core (token de integração), análoga à já existente entre amIA-Ingest e o Core - Overhead operacional inicial: provisionamento Terraform, secrets no Key Vault (tokens de conector do ServiceNow), Variable Groups no Azure DevOps - Latência adicional de um hop a mais (Integration → fila → Core) até o chamado aparecer no domínio, comparado a gravar diretamente no Core

Riscos mitigados: - O padrão de repositório, Dockerfile e pipeline pode ser copiado do amIA-Ingest (mesmo stack Python/FastAPI ou equivalente .NET, a definir na spec de implementação), reduzindo o esforço de bootstrap - O uso de fila (RabbitMQ) entre amIA-Integration e amIA Core absorve picos de webhook sem exigir que o ServiceNow reenvie em caso de lentidão momentânea do Core

Diagrama de responsabilidades

graph LR
    SN[ServiceNow] -->|"webhook customizado\nAuthorization: Bearer token"| GW

    subgraph GW["amIA-Integration"]
        R1[Endpoint de recepção]
        CACHE[(Cache em memória\nClientConnector)]
        R2[Validação de token — hash local]
        R3[Auditoria — ReceivedEvent]
        R4[Transformação para modelo de domínio]
    end

    subgraph CORE["amIA Core"]
        CFG[Domínio ClientConnector\nschema core]
        C1[Consumer CreateOrUpdateTicketCommand]
        C2[Domínio Ticket]
        C3[Auditoria de domínio / RBAC]
    end

    R1 --> R2
    CACHE --> R2
    R2 --> R3 --> R4
    R4 -->|"publica CreateOrUpdateTicketCommand\n(fila de trabalho)"| C1
    C1 --> C2
    C1 --> C3
    CFG -->|"publica ClientConnectorUpserted\n(exchange fanout)"| CACHE

Convenções derivadas desta decisão: - Todo novo conector de sistema externo (Jira, Zendesk, e-mail, etc.) deve ser implementado como um adaptador dentro do amIA-Integration, e não como lógica ad-hoc no amIA Core - O payload raw de cada evento recebido deve ser persistido (ReceivedEvent) antes de qualquer transformação, para permitir reprocessamento e troubleshooting - A comunicação Integration → Core (CreateOrUpdateTicketCommand) deve ocorrer via fila de trabalho RabbitMQ, seguindo o mesmo padrão de integração service-to-service adotado pelo amIA-Ingest (ver ADR-005) - A comunicação Core → Integration para configuração (ClientConnectorUpserted/Deactivated) deve ocorrer via exchange fanout com fila exclusiva por instância, nunca fila de trabalho comum — o dado sincronizado vive na memória de cada processo - Configuração de conector (ClientConnector) deve ser mantida como dado de tenant no amIA-Core, nunca replicada como fonte de verdade em outro serviço - Credenciais e segredos de cada sistema externo (tokens de conector) devem ser armazenados no Azure Key Vault, nunca em configuração versionada nem em texto puro no banco de dados