Pular para o conteúdo

Registry Internals

O registry é um armazenamento de estado versionado e orientado a eventos. Ele mantém histórico completo de versões, suporta transações e propaga mudanças através do event bus.

Entradas são armazenadas como um slice ordenado com um índice de hash map para lookups O(1):

type Entry struct {
ID ID // namespace:name
Kind Kind // Tipo da entrada
Meta attrs.Bag // Metadados
Data payload.Payload // Conteúdo
}

IDs de entrada usam o pacote unique do Go para interning - IDs idênticos compartilham memória.

Cada versão aponta para seu pai. Computação de caminho usa um algoritmo de grafo para encontrar a rota mais curta entre quaisquer duas versões:

flowchart LR
v0[v0] --> v1[v1] --> v2[v2] --> v3[v3] --> vN[vN]

Um changeset é uma lista ordenada de operações transformando um estado em outro:

OperaçãoOriginalEntryPropósito
CreatenilAdicionar nova entrada
Updatevalor antigoModificar existente
Deletevalor deletadoRemover entrada

OriginalEntry permite reversão - updates armazenam o valor anterior, deletes armazenam o que foi removido.

BuildDelta(oldState, newState) gera operações mínimas:

  1. Comparar estados, identificar mudanças
  2. Ordenar deletes em ordem reversa de dependência (dependentes primeiro)
  3. Ordenar creates/updates em ordem direta de dependência (dependências primeiro)

Múltiplos changesets mesclam rastreando estado final por entrada:

Create + Update = Create (com valor atualizado)
Create + Delete = vazio (cancelam)
Update + Delete = Delete
Delete + Create = Update
sequenceDiagram
participant R as Registry
participant B as EventBus
participant H as Handlers
R->>B: registry.begin
loop Cada Operação
R->>B: entry.create/update/delete
B->>H: despachar para listeners
H-->>B: aceitar ou rejeitar
B-->>R: confirmação
end
alt Todos aceitos
R->>B: registry.commit
else Algum rejeitado
R->>B: registry.discard
R->>R: rollback
end

Handlers tem 30 segundos para aceitar ou rejeitar cada operação. Em rejeição, o registry faz rollback computando e aplicando o delta inverso.

Alguns tipos pulam o event bus completamente:

  • registry.entry - Configs de aplicação
  • ns.requirement - Requirements de namespace
  • ns.dependency - Dependências de módulo
  • ns.definition - Metadados do módulo (readme, licença, autores)

Entradas podem declarar dependências de outras entradas. O resolver extrai dependências via padrões registrados:

resolver.RegisterPattern(registry.DependencyPattern{
Path: "meta.server",
AllowWildcard: true,
})

Dependências são extraídas dos campos Meta e Data da entrada, depois usadas para ordenação topológica durante transições de estado.

Backends de histórico:

ImplementaçãoCaso de Uso
SQLitePersistência de produção
MemoryTestes
NilSem histórico

SQLite usa modo WAL com tabelas para versões, changesets (codificados em MessagePack) e metadados.

Computação de caminho encontra a rota mais curta entre versões:

Path(v0, v3) = [v1, v2, v3] // Aplicar changesets para frente
Path(v3, v1) = [v2, v1] // Aplicar changesets reversos

LoadState() reproduz histórico de um baseline sem criar novas versões - usado durante boot.

Motor de busca com caching LRU para pesquisar entradas:

OperadorPrefixoExemplo
Glob(nenhum).kind=function.*
Regex~~meta.path=/api/.*
Contains**meta.tags=backend
Prefix^^meta.name=user
Suffix$$meta.path=Handler

Cache invalida em mudança de versão.