Versionamento dos documentos¶
Identidade lógica, identidade da revisão e número de versão são conceitos distintos. _id de Gateway/Route/Template permanece estável; cada revisão possui seu próprio _id. O número versionNo é um atributo de domínio, não substitui esses identificadores.
Core — editar, publicar e restaurar¶
%%{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
root["Gateway / Route<br/><small>Identidade estável</small>"]:::domain
current["Revisão ACTIVE<br/><small>Configuração publicada</small>"]:::store
draft["Nova revisão DRAFT<br/><small>Novo ObjectId</small>"]:::store
history["Revisão DEPRECATED<br/><small>Histórico recuperável</small>"]:::store
current -->|gatewayId / routeId| root
draft -->|Mesma identidade lógica| root
current -->|Clonar para editar| draft
history -->|Clonar para restaurar| draft
classDef domain stroke-width:1.5px;
classDef store stroke-width:1.5px;
| Operação | Efeito nos documentos |
|---|---|
| Criação | Raiz e revisão inicial; versão inicial 0.1. Flags enabled/favorite da raiz não são o status da revisão |
| Edição de ACTIVE | GatewayActiveToNewDraftExecutor clona com novo _id, incrementa minor da maior versão e limpa contexto de upgrade. Copia rotas ACTIVE para PUBLISHABLE vinculadas ao novo gatewayRevisionId, preservando DRAFT de rota já existente |
| Preparação de rota | DRAFT → PUBLISHABLE; retorno a DRAFT é uma transição distinta. A rota não se torna ACTIVE apenas por estar preparada |
| Promoção de gateway | Valida, deprecia ACTIVE anterior, promove rotas PUBLISHABLE vinculadas ao DRAFT e troca status para ACTIVE. Promoção ordinária incrementa major; primeiro publish habilita a raiz. Emite outbox gateway.published |
| Rollback de gateway | Exige revisão DEPRECATED do mesmo gateway. Cria novo DRAFT, novo ID e versão minor após a maior versão; restaura configurações/rotas e flags de raízes removidas conforme o histórico. Não troca silenciosamente o ACTIVE pela revisão antiga |
| Rollback de rota | Executor próprio cria configuração de trabalho a partir de revisão histórica; segue o vínculo de gateway e as regras de versão da rota. Não presumir restauração física de todo o banco |
| Exclusão | Raízes de rota/categoria podem ser marcadas deleted; exclusão do gateway usa cascata implementada. DEPRECATED não significa exclusão física |
GatewayRevision: DRAFT, ACTIVE, DEPRECATED. RouteRevision: DRAFT, PUBLISHABLE, ACTIVE, DEPRECATED. Os vínculos routeId e gatewayRevisionId precisam ser preservados em leituras históricas; consultar apenas “a última rota” perde a versão do conjunto.
Fontes: promoção, edição, rollback, rollback de rota, seleção/validação, VersionHelper.
Templates — autoria não é publicação de instância¶
%%{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 LR
draft["DRAFT<br/><small>Autoria do modelo</small>"]:::domain
ready["PUBLISHABLE<br/><small>Modelo validado</small>"]:::domain
available["AVAILABLE<br/><small>Versão para consumo</small>"]:::store
draft -->|publish| ready
ready -->|promote| available
classDef domain stroke-width:1.5px;
classDef store stroke-width:1.5px;
unpublish retorna PUBLISHABLE a DRAFT; deprecate leva AVAILABLE a DEPRECATED. O enum não oferece retorno de DEPRECATED a DRAFT. Não documentar rollback de Templates como se fosse o rollback de Core.
RevisionLifecycle.publish() valida e marca PUBLISHABLE. promote() incrementa major e marca AVAILABLE; hooks coordenam rotas, snapshots, catálogo, arquivos e outbox. Índices parciais garantem no máximo uma revisão de trabalho DRAFT/PUBLISHABLE por raiz GatewayTemplate e RouteTemplate. Não restringem a apenas uma revisão histórica publicada por número em toda a base.
TemplatePublicationSnapshotService captura, na mesma sessão dos repositories da promoção, publishedRoutes, publishedCategories e publishedRouteRevisions. Somente revisões de rota AVAILABLE do gateway modelo entram no conjunto publicado. O snapshot registra metadados mutáveis das raízes; futuras mudanças de nome/categoria não devem reconstruir uma publicação antiga por heurística. A leitura de owner para instanciação ocorre separadamente, pelo banco do autor.
Fontes: lifecycle, estados, serviço de revisão, snapshots, leitura do owner, infraestrutura de edição.
Proveniência e upgrade de instâncias¶
%%{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
operation["Operation · TEMPLATE_UPGRADE<br/><small>Tenant consumidor</small>"]:::async
baseline["GatewayRevision ACTIVE<br/><small>Baseline preservado</small>"]:::store
draft["GatewayRevision DRAFT<br/><small>templateState / templateUpgrade</small>"]:::store
wizard["gateway_wizard<br/><small>Setup da nova revisão</small>"]:::store
operation -->|baselineRevisionId na preparação| baseline
operation -->|gatewayRevisionId / operationId| draft
wizard -->|gatewayRevisionId / operationId| draft
classDef store stroke-width:1.5px;
classDef async stroke-width:1.5px;
templateState conserva modelo, revisão fonte, owner e mapas de identidade. sourceVersionNo conserva a versão fonte; versionNo é a evolução local. fullVersion/sourceVersion são projeções de leitura e não campos novos persistidos em GatewayRevision.
O upgrade prepara um DRAFT reservado por Operation. baselineState, pendingRoutes e setupContexts preservam proveniência, configuração pendente e respostas. Revisões pendentes anteriores são depreciadas e substituídas sem reatribuir seus vínculos históricos. IDs novos de revisão de rota são determinísticos por operação/rota, e etapas concluídas não devem ser reaplicadas. Recursos TEMPLATE recebem a configuração alvo; CUSTOM preservam configuração local.
A conclusão da Operation significa preparação do DRAFT, não publicação. A promoção ordinária do Core efetiva o conjunto; promoção de upgrade preserva a versão local do gateway. Raízes de origem Template só têm seus metadados substituídos na promoção. Remoções podem omitir uma associação no novo conjunto e depreciar a revisão anterior, sem apagar a história.
Proveniência incompleta é uma limitação funcional: a recuperação legada usa Operation concluída e mapas unívocos, ou rejeita o upgrade; nomes, paths e posições não servem como identidade. Snapshots históricos inexistentes não são criados retroativamente por suposição.
Os testes RevisionLifecycleTest especificam transições e incremento de versão de Templates. GatewayRouteSnapshotMongoTest cobre membership independente entre upgrades, filtros pelo snapshot, precedência de CUSTOM e categorias da revisão selecionada. São evidências dos contratos documentados, não resultados de testes executados nesta tarefa documental.
Fontes: preparação, aplicação, promoção, Operation/lease, serviços de upgrade. A análise de dados separa riscos de concorrência e histórico das regras implementadas.