Zum Inhalt springen

Registry-Interna

Die Registry ist ein versionierter, ereignisgesteuerter Zustandsspeicher. Sie pflegt eine vollständige Versionshistorie, unterstützt Transaktionen und verbreitet Änderungen über den Event-Bus.

Einträge werden als geordnetes Slice mit einer Hash-Map-Index für O(1)-Lookups gespeichert:

type Entry struct {
ID ID // namespace:name
Kind Kind // Entry-Typ
Meta attrs.Bag // Metadaten
Data payload.Payload // Inhalt
}

Entry-IDs verwenden Gos unique-Paket zum Internieren - identische IDs teilen sich denselben Speicher.

Jede Version zeigt auf ihren Parent. Pfadberechnung verwendet einen Graph-Algorithmus um die kürzeste Route zwischen zwei Versionen zu finden:

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

Ein ChangeSet ist eine geordnete Liste von Operationen, die einen Zustand in einen anderen transformieren:

OperationOriginalEntryZweck
CreatenilNeuen Eintrag hinzufügen
Updatealter WertExistierenden modifizieren
Deletegelöschter WertEintrag entfernen

OriginalEntry ermöglicht Umkehrung - Updates speichern den vorherigen Wert, Deletes speichern was entfernt wurde.

BuildDelta(oldState, newState) generiert minimale Operationen:

  1. Zustände vergleichen, Änderungen identifizieren
  2. Deletes in umgekehrter Abhängigkeitsreihenfolge sortieren (Abhängige zuerst)
  3. Creates/Updates in Vorwärts-Abhängigkeitsreihenfolge sortieren (Abhängigkeiten zuerst)

Mehrere ChangeSets verschmelzen durch Verfolgung des Endzustands pro Eintrag:

Create + Update = Create (mit aktualisiertem Wert)
Create + Delete = ∅ (heben sich auf)
Update + Delete = Delete
Delete + Create = Update
sequenceDiagram
participant R as Registry
participant B as EventBus
participant H as Handlers
R->>B: registry.begin
loop Jede Operation
R->>B: entry.create/update/delete
B->>H: an Listener dispatchen
H-->>B: akzeptieren oder ablehnen
B-->>R: Bestätigung
end
alt Alle akzeptiert
R->>B: registry.commit
else Einer abgelehnt
R->>B: registry.discard
R->>R: Rollback
end

Handler haben 30 Sekunden um jede Operation zu akzeptieren oder abzulehnen. Bei Ablehnung führt die Registry ein Rollback durch, indem sie das inverse Delta berechnet und anwendet.

Einige Arten überspringen den Event-Bus komplett:

  • registry.entry - Anwendungskonfigurationen
  • ns.requirement - Namespace-Requirements
  • ns.dependency - Modul-Abhängigkeiten
  • ns.definition - Modul-Metadaten (Readme, Lizenz, Autoren)

Einträge können Abhängigkeiten von anderen Einträgen deklarieren. Der Resolver extrahiert Abhängigkeiten über registrierte Muster:

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

Abhängigkeiten werden aus Entry-Meta- und -Data-Feldern extrahiert und dann für topologische Sortierung während Zustandsübergängen verwendet.

History-Backends:

ImplementierungAnwendungsfall
SQLiteProduktions-Persistenz
MemoryTesten
NilKeine Historie

SQLite verwendet WAL-Modus mit Tabellen für Versionen, ChangeSets (MessagePack-kodiert) und Metadaten.

Pfadberechnung findet die kürzeste Route zwischen Versionen:

Path(v0, v3) = [v1, v2, v3] // ChangeSets vorwärts anwenden
Path(v3, v1) = [v2, v1] // Umgekehrte ChangeSets anwenden

LoadState() spielt Historie von einer Baseline ab ohne neue Versionen zu erstellen - wird beim Boot verwendet.

Query-Engine mit LRU-Caching für Entry-Suche:

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

Cache invalidiert bei Versionsänderung.