Ir al contenido

Internos del Registry

El registry es un almacén de estado versionado y orientado a eventos. Mantiene historial de versiones completo, soporta transacciones, y propaga cambios a través del event bus.

Las entradas se almacenan como un slice ordenado con un índice de hash map para lookups O(1):

type Entry struct {
ID ID // namespace:name
Kind Kind // Tipo de entrada
Meta attrs.Bag // Metadatos
Data payload.Payload // Contenido
}

Los IDs de entrada usan el paquete unique de Go para interning—IDs idénticos comparten memoria.

Cada versión apunta a su padre. El cálculo de ruta usa un algoritmo de grafos para encontrar la ruta más corta entre cualquier dos versiones:

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

Un changeset es una lista ordenada de operaciones que transforman un estado a otro:

OperaciónOriginalEntryPropósito
CreatenilAgregar nueva entrada
Updatevalor anteriorModificar existente
Deletevalor eliminadoRemover entrada

OriginalEntry permite reversión—updates almacenan el valor previo, deletes almacenan lo que fue removido.

BuildDelta(oldState, newState) genera operaciones mínimas:

  1. Comparar estados, identificar cambios
  2. Ordenar deletes en orden inverso de dependencias (dependientes primero)
  3. Ordenar creates/updates en orden de dependencias (dependencias primero)

Múltiples changesets se fusionan rastreando estado final por entrada:

Create + Update = Create (con valor actualizado)
Create + Delete = ∅ (se cancelan)
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 Operación
R->>B: entry.create/update/delete
B->>H: dispatch a listeners
H-->>B: aceptar o rechazar
B-->>R: confirmación
end
alt Todo aceptado
R->>B: registry.commit
else Alguno rechazado
R->>B: registry.discard
R->>R: rollback
end

Los handlers tienen 30 segundos para aceptar o rechazar cada operación. En rechazo, el registry hace rollback calculando y aplicando el delta inverso.

Algunos kinds omiten el event bus completamente:

  • registry.entry - Configs de aplicación
  • ns.requirement - Requisitos de namespace
  • ns.dependency - Dependencias de módulos
  • ns.definition - Metadatos del módulo (readme, licencia, autores)

Las entradas pueden declarar dependencias en otras entradas. El resolver extrae dependencias vía patrones registrados:

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

Las dependencias se extraen de campos Meta y Data de entradas, luego se usan para ordenamiento topológico durante transiciones de estado.

Backends de historial:

ImplementaciónCaso de Uso
SQLitePersistencia de producción
MemoryTesting
NilSin historial

SQLite usa modo WAL con tablas para versiones, changesets (codificados MessagePack), y metadatos.

El cálculo de ruta encuentra la ruta más corta entre versiones:

Path(v0, v3) = [v1, v2, v3] // Aplicar changesets hacia adelante
Path(v3, v1) = [v2, v1] // Aplicar changesets revertidos

LoadState() reproduce historial desde una línea base sin crear nuevas versiones—usado durante boot.

Motor de consultas con cache LRU para buscar entradas:

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

Cache se invalida en cambio de versión.