BFF — ficha piloto¶
Repositório: totvs-gateway-bff · Estado: Implementado · Responsável: time Gateway.
Versão documentada: 2f16f060de642dee03c61a0776caa11cc9167fbe (develop).
Esta ficha descreve o container BFF, seus contratos e suas fronteiras. As relações da plataforma estão no inventário de dependências; documentos e caches estão na modelagem de dados.
Responsabilidade¶
O BFF expõe APIs administrativas para o cliente administrativo externo, encaminha chamadas com contexto de tenant e adapta/agrega respostas. Também oferece fachadas públicas de token, JWKS, arquivos e documentação de catálogo. Core, Templates, Registry e serviços de políticas continuam responsáveis pelas respectivas regras e persistência.
O BFF não é o Broker: não orquestra a execução HTTP das APIs publicadas. Possui composições específicas, como enriquecimento de catálogo e obtenção de templates, além de relay SSE e transporte de arquivos. Fontes: integrações, CatalogForwardingService, configuração downstream.
O inventário completo das integrações do BFF identifica os 18 destinos HTTP e diferencia serviços de configuração, apoio compartilhado e domínios externos. As visões por domínio apresentam as chamadas verificadas nos forwarders.
Tecnologia e organização¶
Java 21, Quarkus 3.28.2, REST/Jackson, CDI, Bean Validation, SmallRye JWT/OpenAPI/Health/Fault Tolerance, Redis e OpenTelemetry. O POM referencia bibliotecas corporativas Accounts e Redis SNAPSHOT. DTOs, mappers e assemblers implementam as adaptações. O cliente compartilhado para forwarders usa java.net.http.HttpClient; Authenticator usa MicroProfile Rest Client.
| Área | Responsabilidade |
|---|---|
integration/<destino>/resource |
HTTP, parâmetros, formatos, respostas e OpenAPI |
integration/<destino>/service |
Encaminhamento, composição e adaptação específicas |
integration/core/.../dto, mappers e assemblers |
Contratos e transformação das respostas administrativas |
shared/http |
Cliente contextual, envelopes, binários, streams e relay SSE |
integration/ipaas/security |
Filtro de acesso iPaaS |
tenant/security |
Resolução do tenant e exceção pública independente |
security |
Identidade, parser JWT, infraestrutura de autorização e propagação |
shared/cache e integration/ratelimit/security |
Alias da resposta e limites de endpoints de identidade |
Fontes: POM, pacote BFF, TenantAwareHttpClient.
Retângulos representam serviços ou grupos nomeados; cilindros, armazenamento; caixas com bordas duplas, mensageria; formas arredondadas, sistemas externos. As setas indicam chamadas ou transporte de dados, conforme o rótulo.
Componentes — C4 nível 3¶
Caminho de transporte¶
Como a entrada administrativa chega ao serviço de domínio? A visão destaca as três responsabilidades do BFF, sem misturar caches e streaming.
%%{init: {"theme":"base","themeVariables":{"fontFamily":"Arial, sans-serif","fontSize":"16px","lineColor":"#64748b","primaryTextColor":"#172b4d"},"flowchart":{"htmlLabels":true,"useMaxWidth":true,"nodeSpacing":28,"rankSpacing":38,"padding":14,"curve":"linear","subGraphTitleMargin":{"top":12,"bottom":16}},"sequence":{"useMaxWidth":true,"actorMargin":28,"width":145,"height":45,"diagramMarginX":32,"diagramMarginY":12,"messageMargin":24,"wrap":true,"mirrorActors":false},"themeCSS":".edgeLabel, .edgeLabel p {background-color:var(--diagram-surface)!important;color:var(--diagram-text)!important}\n.cluster rect {fill:var(--diagram-group)!important;stroke:var(--diagram-line)!important}\n.cluster-label span, .cluster-label text {color:var(--diagram-text)!important;fill:var(--diagram-text)!important}\n.flowchart-link, .actor-line {stroke:var(--diagram-line)!important}\nmarker path {fill:var(--diagram-line)!important;stroke:var(--diagram-line)!important}\n.actor {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\ntext.actor, .messageText, .loopText, .labelText {fill:var(--diagram-text)!important}\n.messageLine0, .messageLine1, .loopLine {stroke:var(--diagram-line)!important}\n.labelBox {fill:var(--diagram-surface)!important;stroke:var(--diagram-line)!important}\n.note {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.noteText {fill:var(--diagram-text)!important}\n.nodeLabel, .nodeLabel p, .nodeLabel span {color:var(--diagram-text)!important}\n.node .nodeLabel, .node .nodeLabel p {font-size:17px;font-weight:600;line-height:1.4;text-align:center}\n.nodeLabel small {display:inline-block;margin-top:4px;font-size:15px;font-weight:400;line-height:1.4;color:var(--diagram-description)!important}\ntext.actor, text.actor tspan {font-size:17px;font-weight:600}\n.node.entry rect, .node.entry polygon, .node.entry path, .node.entry circle {fill:var(--diagram-entry)!important;stroke:var(--diagram-entry-border)!important}\n.node.domain rect, .node.domain polygon, .node.domain path, .node.domain circle {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\n.node.security rect, .node.security polygon, .node.security path, .node.security circle {fill:var(--diagram-security)!important;stroke:var(--diagram-security-border)!important}\n.node.store rect, .node.store polygon, .node.store path, .node.store circle {fill:var(--diagram-store)!important;stroke:var(--diagram-store-border)!important}\n.node.async rect, .node.async polygon, .node.async path, .node.async circle {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.node.external rect, .node.external polygon, .node.external path, .node.external circle {fill:var(--diagram-external)!important;stroke:var(--diagram-external-border)!important}\n text.actor tspan, .loopText tspan, .labelText tspan, .messageText tspan, .noteText tspan {fill:var(--diagram-text)!important}\n\n"}}%%
flowchart TB
client(["Cliente administrativo"]):::external
subgraph boundary["BFF · componentes"]
entry["JWT / Resources REST<br/><small>Entrada e contratos</small>"]:::entry
forward["Forwarders e mappers<br/><small>Composição</small>"]:::domain
http["TenantAwareHttpClient<br/><small>Transporte contextual</small>"]:::domain
entry -->|Delega| forward
forward -->|Prepara chamada| http
end
downstream(["Serviços de destino"]):::external
client -->|HTTP| entry
http -->|Tenant e tracing| downstream
classDef entry stroke-width:1.5px;
classDef domain stroke-width:1.5px;
classDef external stroke-width:1.5px;
Resolução de tenant e limites¶
Quem consulta o tenant e onde os limites são contabilizados? O filtro resolve host somente quando a exceção pública aplicável exige esse contexto.
%%{init: {"theme":"base","themeVariables":{"fontFamily":"Arial, sans-serif","fontSize":"16px","lineColor":"#64748b","primaryTextColor":"#172b4d"},"flowchart":{"htmlLabels":true,"useMaxWidth":true,"nodeSpacing":28,"rankSpacing":38,"padding":14,"curve":"linear","subGraphTitleMargin":{"top":12,"bottom":16}},"sequence":{"useMaxWidth":true,"actorMargin":28,"width":145,"height":45,"diagramMarginX":32,"diagramMarginY":12,"messageMargin":24,"wrap":true,"mirrorActors":false},"themeCSS":".edgeLabel, .edgeLabel p {background-color:var(--diagram-surface)!important;color:var(--diagram-text)!important}\n.cluster rect {fill:var(--diagram-group)!important;stroke:var(--diagram-line)!important}\n.cluster-label span, .cluster-label text {color:var(--diagram-text)!important;fill:var(--diagram-text)!important}\n.flowchart-link, .actor-line {stroke:var(--diagram-line)!important}\nmarker path {fill:var(--diagram-line)!important;stroke:var(--diagram-line)!important}\n.actor {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\ntext.actor, .messageText, .loopText, .labelText {fill:var(--diagram-text)!important}\n.messageLine0, .messageLine1, .loopLine {stroke:var(--diagram-line)!important}\n.labelBox {fill:var(--diagram-surface)!important;stroke:var(--diagram-line)!important}\n.note {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.noteText {fill:var(--diagram-text)!important}\n.nodeLabel, .nodeLabel p, .nodeLabel span {color:var(--diagram-text)!important}\n.node .nodeLabel, .node .nodeLabel p {font-size:17px;font-weight:600;line-height:1.4;text-align:center}\n.nodeLabel small {display:inline-block;margin-top:4px;font-size:15px;font-weight:400;line-height:1.4;color:var(--diagram-description)!important}\ntext.actor, text.actor tspan {font-size:17px;font-weight:600}\n.node.entry rect, .node.entry polygon, .node.entry path, .node.entry circle {fill:var(--diagram-entry)!important;stroke:var(--diagram-entry-border)!important}\n.node.domain rect, .node.domain polygon, .node.domain path, .node.domain circle {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\n.node.security rect, .node.security polygon, .node.security path, .node.security circle {fill:var(--diagram-security)!important;stroke:var(--diagram-security-border)!important}\n.node.store rect, .node.store polygon, .node.store path, .node.store circle {fill:var(--diagram-store)!important;stroke:var(--diagram-store-border)!important}\n.node.async rect, .node.async polygon, .node.async path, .node.async circle {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.node.external rect, .node.external polygon, .node.external path, .node.external circle {fill:var(--diagram-external)!important;stroke:var(--diagram-external-border)!important}\n text.actor tspan, .loopText tspan, .labelText tspan, .messageText tspan, .noteText tspan {fill:var(--diagram-text)!important}\n\n"}}%%
flowchart TB
jwt["IpaasJwtFilter"]:::security
http["TenantAwareHttpClient"]:::domain
quota["RateLimitService"]:::security
tenant["TenantResolver"]:::security
redis[("Redis<br/><small>Buckets de limite</small>")]:::store
jwt -->|Host público| tenant
http -->|Contexto| tenant
quota -->|Owner| tenant
quota -->|Lua| redis
classDef domain stroke-width:1.5px;
classDef security stroke-width:1.5px;
classDef store stroke-width:1.5px;
| Componente / relação | Responsabilidade complementar |
|---|---|
| SmallRye JWT / IpaasJwtFilter → Resources | Validar acesso antes do resource |
| Resources → RateLimitService | Aplicar limite nos endpoints configurados |
| Resources → SseRelayService | Encaminhar operações SSE |
| SseRelayService → TenantAwareHttpClient | Transporte com Bearer e Last-Event-ID |
| DnsAliasResponseFilter → TenantResolver | Obter tenant para enriquecimento |
| RateLimitService → Redis | Contabilizar buckets com Lua (INCR / EXPIRE) |
O desenho apresenta componentes lógicos dentro do mesmo processo. ResourceAuthorizationInterceptor existe, mas não é desenhado como etapa obrigatória: não há aplicações de @RequiresResource aos endpoints. Análise AA-001.
APIs e consumidores¶
Root HTTP /; porta declarada 8080. As constantes ApiPaths e ApiPathsV2 definem bases /gateway/api/v1 e /gateway/api/v2. A versão do endpoint BFF não determina a versão do downstream.
| Família / path | Métodos e propósito | Destino principal |
|---|---|---|
/gateway/api/v1/gateways e /{gatewayId} |
CRUD, habilitação, revisões, promoção/rollback | Core |
.../gateways/{gatewayId}/routes e /categories |
CRUD e ciclo de vida de rotas/categorias | Core |
.../gateways/{gatewayId}/wizard |
Leitura/atualização do wizard | Core |
/gateway/api/v2/gateways, rotas e /resources |
Contratos v2 e adaptações específicas | Core / iPaaS / JSON Handler |
/gateway/api/v1/authorizations |
Administração de autorizações | Security |
/gateway/api/v1/ip-policies, /rate-limits |
CRUD de políticas | IP Policy / Rate Limit |
/gateway/api/v1/gateway-templates e subresources |
Autoria, revisões, publicação e instanciação normal/de teste | Template Service |
POST .../gateways/{gatewayId}/template-upgrade |
Encaminhar solicitação de upgrade | Template Service |
GET /gateway/api/v1/operations/gateways/{gatewayId}/events |
SSE de progresso | Template Service |
POST /gateway/api/v1/operations/gateways/{gatewayId}/retry |
Solicitar nova tentativa | Template Service |
/gateway/api/v1/global-registry/catalog |
Catálogo e favoritos; documentação em subresource público | Registry + Core + OpenAPI Doc |
POST /gateway/api/v1/catalog/obtain/{catalogItemId} |
Obter definição e solicitar instanciação | Registry + Template Service |
/gateway/api/v1/tenant-aliases |
Administração de aliases | Registry |
/gateway/api/v1/resources, /json-handler |
Recursos/importação e transformação de schemas | iPaaS / Importer / JSON Handler |
/gateway/api/v1/monitoring |
Logs/métricas | Monitoring |
/gateway/api/v1/oauth2 |
Operações administrativas de identidade | Auth Provider |
/gateway/api/v1/object-storage/files |
Upload, leitura, metadados e publicação | Object Storage |
/gateway/api/v1/tunnels |
Administração e download de túnel | Tunnel |
Tabela de famílias, não especificação exaustiva. Métodos e parâmetros de cada subresource estão nos resources atuais. Referências específicas: OperationResource, TemplateUpgradeResource, CatalogResource.
Exceções públicas comprovadas¶
| Endpoint | Contrato de entrada e resposta | Contexto |
|---|---|---|
GET /.well-known/jwks.json |
JSON de certificados do Auth Provider | PermitAll; tenant resolvido do host pelo filtro |
POST /auth/token |
Form URL-encoded; client credentials; JSON de token; bucket token |
PermitAll; Basic ou campos do formulário, conforme contrato; tenant do host |
GET /gateway/api/v1/object-storage/public/files/{fileId} |
Arquivo público | PermitAll + TenantIndependent; leitura sem autenticação ou contexto de tenant |
GET /gateway/api/v1/global-registry/catalog/{catalogItemId}/documentation |
JSON documental | PermitAll + TenantIndependent; owner obtido no Registry |
GET /static/catalog/{catalogItemId} |
HTML | PermitAll + TenantIndependent |
GET /static/catalog/templates/{gatewayTemplateId}?versionNo=... |
Resolve publicação e responde 303 com Location para a página do item |
PermitAll + TenantIndependent; erros 400/404/409 conforme contrato |
Fontes: OAuth público, arquivos públicos, catálogo, páginas estáticas.
Identidade, autorização e tenant¶
IpaasJwtFilter ignora os prefixos de saúde/Swagger listados no código. Para recursos comuns, exige token presente e claim apps contendo ipaas (comparação sem distinção de caixa); retorna 401 sem token e 403 sem acesso ao app. A assinatura/issuer pertencem à configuração JWT/SmallRye e parser customizado; a checagem de apps não os substitui.
Para PermitAll, o filtro não exige esse JWT. Resolve tenant pelo primeiro label do host, exceto quando TenantIndependent é aplicado. TenantResolver.getTenantId() usa o tenant previamente resolvido ou claim companyId, rejeitando ausência/branco. O parâmetro x-tenant-id recebido do cliente não é a fonte dessa resolução no cliente compartilhado.
Fontes: IpaasJwtFilter, TenantResolver, parser, properties.
ResourceAuthorizationService implementa consulta ao Authenticator usando usuário/tenant, comparação de grupo/recurso e bypass para identidade técnica. RESOURCE_AUTHORIZATION_ENABLED tem default true, mas a flag não aplica a anotação aos endpoints. Não há evidência de RBAC geral concluído. O Bearer do usuário é propagado no AuthenticatorClient por provider explícito; não afirmar propagação automática em todos os clients.
Fontes: serviço de autorização, bypass, AuthenticatorClient, teste de isolamento Accounts/Authenticator.
Encaminhamento e transformações¶
TenantAwareHttpClient estabelece conexão com timeout de 30 s e deadline de 30 s para requests ordinários. Inclui x-tenant-id, JSON/Accept e propagação OTel. Headers extras são aplicados com setHeader, podendo substituir defaults em integrações específicas. O builder público não resolve tenant automaticamente; chamadas documentais podem acrescentar owner explicitamente.
getStringResponse lança DownstreamHttpException para status >= 400. exchange devolve status/body sem lançar por status HTTP. exchangeBytes e exchangeStream tratam corpo binário/stream e erros. Não há retry genérico implementado nesse cliente: presença de Fault Tolerance no POM não prova retry de todas as operações.
Fontes: cliente HTTP, mapper de erro.
Composição de catálogo¶
findPaginated combina catálogo do Registry com estado de favoritos/instâncias consultado no Core. O enriquecimento adiciona favorite, acquired e acquiredGateways; remove o alias antigo aquired e o gatewayId singular. obtain deriva owner/template/versão da resposta do Registry e envia x-tenant-template-owner-id e catalog-id ao Template Service.
documentation resolve owner no Registry, depois consulta OpenAPI Doc com x-tenant-id desse owner. staticPage chama Web Pages por GET com body JSON {}; staticPageByTemplate resolve catálogo por versão e redireciona no mesmo origin. São contratos implementados, não recomendações genéricas de desenho de API.
Fontes: CatalogForwardingService, CatalogStaticResource, CatalogDocumentationTest.
Sequência interna ilustrada a partir do cliente real¶
Esta sequência descreve apenas a colaboração interna de forwarding com getStringResponse, após a validação de entrada; não é um fluxo E2E de negócio completo nem se aplica a todos os métodos de transporte.
Preparação do request¶
Como o cliente contextual adiciona tenant e tracing? O Resource já delegou a operação ao Forwarder.
%%{init: {"theme":"base","themeVariables":{"fontFamily":"Arial, sans-serif","fontSize":"16px","lineColor":"#64748b","primaryTextColor":"#172b4d"},"flowchart":{"htmlLabels":true,"useMaxWidth":true,"nodeSpacing":28,"rankSpacing":38,"padding":14,"curve":"linear","subGraphTitleMargin":{"top":12,"bottom":16}},"sequence":{"useMaxWidth":true,"actorMargin":28,"width":145,"height":45,"diagramMarginX":32,"diagramMarginY":12,"messageMargin":24,"wrap":true,"mirrorActors":false},"themeCSS":".edgeLabel, .edgeLabel p {background-color:var(--diagram-surface)!important;color:var(--diagram-text)!important}\n.cluster rect {fill:var(--diagram-group)!important;stroke:var(--diagram-line)!important}\n.cluster-label span, .cluster-label text {color:var(--diagram-text)!important;fill:var(--diagram-text)!important}\n.flowchart-link, .actor-line {stroke:var(--diagram-line)!important}\nmarker path {fill:var(--diagram-line)!important;stroke:var(--diagram-line)!important}\n.actor {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\ntext.actor, .messageText, .loopText, .labelText {fill:var(--diagram-text)!important}\n.messageLine0, .messageLine1, .loopLine {stroke:var(--diagram-line)!important}\n.labelBox {fill:var(--diagram-surface)!important;stroke:var(--diagram-line)!important}\n.note {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.noteText {fill:var(--diagram-text)!important}\n.nodeLabel, .nodeLabel p, .nodeLabel span {color:var(--diagram-text)!important}\n.node .nodeLabel, .node .nodeLabel p {font-size:17px;font-weight:600;line-height:1.4;text-align:center}\n.nodeLabel small {display:inline-block;margin-top:4px;font-size:15px;font-weight:400;line-height:1.4;color:var(--diagram-description)!important}\ntext.actor, text.actor tspan {font-size:17px;font-weight:600}\n.node.entry rect, .node.entry polygon, .node.entry path, .node.entry circle {fill:var(--diagram-entry)!important;stroke:var(--diagram-entry-border)!important}\n.node.domain rect, .node.domain polygon, .node.domain path, .node.domain circle {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\n.node.security rect, .node.security polygon, .node.security path, .node.security circle {fill:var(--diagram-security)!important;stroke:var(--diagram-security-border)!important}\n.node.store rect, .node.store polygon, .node.store path, .node.store circle {fill:var(--diagram-store)!important;stroke:var(--diagram-store-border)!important}\n.node.async rect, .node.async polygon, .node.async path, .node.async circle {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.node.external rect, .node.external polygon, .node.external path, .node.external circle {fill:var(--diagram-external)!important;stroke:var(--diagram-external-border)!important}\n text.actor tspan, .loopText tspan, .labelText tspan, .messageText tspan, .noteText tspan {fill:var(--diagram-text)!important}\n\n"}}%%
sequenceDiagram
participant F as Forwarder
participant H as Cliente HTTP
participant T as TenantResolver
F->>H: buildGet / buildPost
H->>T: getTenantId()
T-->>H: companyId / contexto
H-->>F: Request contextual
Transporte e resultado¶
Como o status downstream retorna à camada de aplicação? O mesmo cliente executa o request preparado.
%%{init: {"theme":"base","themeVariables":{"fontFamily":"Arial, sans-serif","fontSize":"16px","lineColor":"#64748b","primaryTextColor":"#172b4d"},"flowchart":{"htmlLabels":true,"useMaxWidth":true,"nodeSpacing":28,"rankSpacing":38,"padding":14,"curve":"linear","subGraphTitleMargin":{"top":12,"bottom":16}},"sequence":{"useMaxWidth":true,"actorMargin":28,"width":145,"height":45,"diagramMarginX":32,"diagramMarginY":12,"messageMargin":24,"wrap":true,"mirrorActors":false},"themeCSS":".edgeLabel, .edgeLabel p {background-color:var(--diagram-surface)!important;color:var(--diagram-text)!important}\n.cluster rect {fill:var(--diagram-group)!important;stroke:var(--diagram-line)!important}\n.cluster-label span, .cluster-label text {color:var(--diagram-text)!important;fill:var(--diagram-text)!important}\n.flowchart-link, .actor-line {stroke:var(--diagram-line)!important}\nmarker path {fill:var(--diagram-line)!important;stroke:var(--diagram-line)!important}\n.actor {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\ntext.actor, .messageText, .loopText, .labelText {fill:var(--diagram-text)!important}\n.messageLine0, .messageLine1, .loopLine {stroke:var(--diagram-line)!important}\n.labelBox {fill:var(--diagram-surface)!important;stroke:var(--diagram-line)!important}\n.note {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.noteText {fill:var(--diagram-text)!important}\n.nodeLabel, .nodeLabel p, .nodeLabel span {color:var(--diagram-text)!important}\n.node .nodeLabel, .node .nodeLabel p {font-size:17px;font-weight:600;line-height:1.4;text-align:center}\n.nodeLabel small {display:inline-block;margin-top:4px;font-size:15px;font-weight:400;line-height:1.4;color:var(--diagram-description)!important}\ntext.actor, text.actor tspan {font-size:17px;font-weight:600}\n.node.entry rect, .node.entry polygon, .node.entry path, .node.entry circle {fill:var(--diagram-entry)!important;stroke:var(--diagram-entry-border)!important}\n.node.domain rect, .node.domain polygon, .node.domain path, .node.domain circle {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\n.node.security rect, .node.security polygon, .node.security path, .node.security circle {fill:var(--diagram-security)!important;stroke:var(--diagram-security-border)!important}\n.node.store rect, .node.store polygon, .node.store path, .node.store circle {fill:var(--diagram-store)!important;stroke:var(--diagram-store-border)!important}\n.node.async rect, .node.async polygon, .node.async path, .node.async circle {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.node.external rect, .node.external polygon, .node.external path, .node.external circle {fill:var(--diagram-external)!important;stroke:var(--diagram-external-border)!important}\n text.actor tspan, .loopText tspan, .labelText tspan, .messageText tspan, .noteText tspan {fill:var(--diagram-text)!important}\n\n"}}%%
sequenceDiagram
participant F as Forwarder
participant H as Cliente HTTP
participant D as Serviço destino
F->>H: getStringResponse
H->>D: HTTP
D-->>H: status + body
alt status >= 400
H-->>F: Erro downstream
else sucesso
H-->>F: body
end
Em caso de erro, DownstreamHttpException atravessa o Forwarder até o Resource e o ExceptionMapper conserva status/body. No sucesso, o Forwarder adapta o resultado quando aplicável e o devolve ao Resource. Cliente HTTP nos dois desenhos corresponde a TenantAwareHttpClient.
SSE, arquivos e efeitos assíncronos¶
O BFF expõe SSE de progresso de instanciação/upgrade e relay para o Template Service. buildStreamGet captura tenant, Bearer e Last-Event-ID, sem deadline ordinário de 30 s. SseRelayService usa publisher HTTP e controle de demanda conforme a escrita; cancela subscription/exchange quando o cliente encerra. Resposta 200 sem text/event-stream vira 502. O relay transmite eventos; não garante replay, conclusão de operação ou idempotência do produtor.
O BFF não persiste uma operação nem consome RabbitMQ para esse relay. A aceitação/retry de operação e sua conclusão pertencem ao Template Service. Binários e multipart usam caminhos de transporte próprios; não descrever tudo como serialização JSON.
Fontes: SseRelayService, OperationStreamService, Object Storage integration, OperationStreamTest.
Estado local e cache¶
Não foi encontrado ownership de documentos MongoDB no BFF. O estado relevante identificado é contexto por request e Redis para mecanismos auxiliares.
| Uso | Implementação | Política observada no código |
|---|---|---|
Header dns-alias |
Lookup tenant-alias:<tenantId> |
Timeout 30 ms; erro/miss omite enriquecimento; TTL de escrita não determinado por esse leitor |
| Limite por bucket | rate-limit:<tenantId>:<bucket> |
Lua INCR e EXPIRE na primeira ocorrência; excesso gera 429 |
| Redis indisponível no limite BFF | RedisRateLimitCache retorna contagem 0 |
Fail-open explícito; não confundir com quota do Checker |
Fontes: resolver de alias, filtro de resposta, RateLimitService, Lua/fallback.
Erros e garantias¶
| Situação | Resultado comprovado | Fonte |
|---|---|---|
| Token ausente no filtro | 401, JSON error/message |
IpaasJwtFilter |
Sem apps=ipaas |
403, JSON error/message |
IpaasJwtFilter |
companyId ausente |
403 pelo mapper | MissingJwtClaimExceptionMapper |
| Host público inválido | 400 pelo mapper | TenantResolutionExceptionMapper |
| Bucket excedido | 429, TOO_MANY_REQUESTS |
RateLimitExceededExceptionMapper |
| DownstreamHttpException | Preserva status/body, Content-Type JSON | Mapper |
| IOException/interrupção no cliente ordinário | IllegalStateException; não há normalização genérica explícita para 502 nesse cliente | TenantAwareHttpClient |
| SSE 200 com tipo inválido | 502 | SseRelayService |
Não há envelope único de erro comprovado para todas as famílias. Anotações OpenAPI precisam ser comparadas com mappers e transporte; não concluir que todo erro de rede é 502. Retry/idempotência de operações compostas dependem dos serviços chamados; não há transação distribuída BFF entre Core, Registry e Templates.
Configuração e deployment¶
Ambientes separados: defaults abaixo vêm do código e não comprovam valores efetivos de DEV, STAGING ou PROD. O deployment descrito a seguir pertence exclusivamente ao ambiente local; a documentação da infraestrutura e dos deployments corporativos é mantida pelo TOTVS Apps.
| Configuração | Finalidade / default relevante |
|---|---|
GATEWAY_*_BASE_URL e CONNECTOR_* |
URLs downstream; defaults incluem localhost/ambientes de desenvolvimento; conferir overrides |
ACCOUNTS_JWKS_URL |
JWKS JWT; fallback compatível ACCOUNTS_JKWS_URL |
ACCOUNTS_JWT_ISSUER |
Default literal *.fluig.io; não é padrão wildcard de matching |
GATEWAY_JWT_JOSE4J_RESPONSE_BODY_SIZE_LIMIT |
Default 10485760 caracteres para resposta JWKS |
RESOURCE_AUTHORIZATION_ENABLED |
Default true para mecanismo quando acionado |
AUTHENTICATOR_URL |
Base do client de permissões |
BFF_RATE_LIMIT_* |
Buckets token/emit-token/test-token/test-discovery; defaults de limite 5 e TTL 60 s nas properties |
QUARKUS_OTEL_ENABLED, traces e metrics |
Defaults false nas properties; deployment pode sobrescrever |
OTEL_EXPORTER_OTLP_ENDPOINT |
Exportador OTLP gRPC, conforme ambiente |
Nenhuma credencial foi reproduzida. Propriedades proxy permitem X-Forwarded-* e host encaminhado; fronteira confiável depende do deployment. Fontes: application.properties, DownstreamServiceConfig.
Deployment declarado no ambiente local¶
O chart local usa Deployment/Service na porta 8080, ConfigMap/Secret, imagem default bff:develop e valores de réplica 1. Probes: /gateway/q/health/started, /ready, /live. Recursos default: requests 250m/256Mi, limits 1 CPU/1Gi. Esses são valores declarados, não consumo medido. O Ingress contém rewrite/CORS locais; não assumir comportamento produtivo equivalente. Redis health está desabilitado nas properties, logo readiness não comprova saúde Redis.
Fontes: deploy.yaml, values, Ingress, ConfigMap. O DNS de Monitoring e a falta de alguns overrides documentais estão registrados em AA-006.
Observabilidade e testes¶
O cliente injeta contexto OTel; há logging de relay SSE, falhas de cache e autorização. Exportação, retenção e dashboards dependem da configuração de observabilidade do ambiente. OpenAPI está configurado em /swagger, UI em /swagger-ui e endpoints não funcionais em /gateway/q.
| Teste existente | Evidência de cenário |
|---|---|
| IpaasJwtFilterTest | Exceções públicas e filtragem |
| TenantResolverTest | Claim e resolução de host |
| AuthClientIsolationTest | Accounts mantém Basic; Authenticator recebe Bearer do usuário |
| CatalogDocumentationTest | Owner documental e preservação de status/body |
| CatalogObtainTest | Composição de obtenção |
| OperationStreamTest | Transporte SSE |
| RedisRateLimitCacheTest | Redis e fallback |
| GatewayResponseAssemblerTest | Adaptação da resposta administrativa |
Testes foram consultados, não executados nesta tarefa documental. Para validação de código em tarefa própria, usar Java 21 e Maven Wrapper no repositório BFF, conforme seu AGENTS.md; não há build único do workspace.
Pendências de confirmação¶
Execução e imagem atuais; proteção efetiva dos endpoints internos; aplicação completa de autorização por recurso; limites operacionais de SSE/caches; compatibilidade dos URLs do chart e clientes; autorização geral cross-tenant. Os registros de análise acompanham essas questões sem alterar a implementação.