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.
Entry-Speicherung
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.
Versionskette
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]
ChangeSets
Ein ChangeSet ist eine geordnete Liste von Operationen, die einen Zustand in einen anderen transformieren:
| Operation | OriginalEntry | Zweck |
|---|---|---|
| Create | nil | Neuen Eintrag hinzufügen |
| Update | alter Wert | Existierenden modifizieren |
| Delete | gelöschter Wert | Eintrag entfernen |
OriginalEntry ermöglicht Umkehrung - Updates speichern den vorherigen Wert, Deletes speichern was entfernt wurde.
Deltas erstellen
BuildDelta(oldState, newState) generiert minimale Operationen:
- Zustände vergleichen, Änderungen identifizieren
- Deletes in umgekehrter Abhängigkeitsreihenfolge sortieren (Abhängige zuerst)
- Creates/Updates in Vorwärts-Abhängigkeitsreihenfolge sortieren (Abhängigkeiten zuerst)
Squashing
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
Transaktionen
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.
Nicht-propagierende Einträge
Einige Arten überspringen den Event-Bus komplett:
registry.entry- Anwendungskonfigurationenns.requirement- Namespace-Requirementsns.dependency- Modul-Abhängigkeitenns.definition- Modul-Metadaten (Readme, Wiki, Lizenz, Autoren)
Abhängigkeitsauflösung
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.
Versionshistorie
History-Backends:
| Implementierung | Anwendungsfall |
|---|---|
| SQLite | Produktions-Persistenz |
| PostgreSQL | Produktions-Persistenz, geteilt über Knoten |
| Memory | Standard, wenn history_type nicht gesetzt ist; Testen |
| Nil | Keine Historie |
SQLite verwendet WAL-Modus mit Tabellen für Versionen, ChangeSets (MessagePack-kodiert) und Metadaten. PostgreSQL wird mit registry.history_type: postgres plus history_dsn/history_schema ausgewählt (siehe Konfiguration).
Die Historie persistiert auch die exakte Abhängigkeitsauflösung jeder Version: Wenn eine ns.dependency-Änderung angewendet wird, wird der aufgelöste Modulgraph inhaltsadressiert neben dem ChangeSet gespeichert. Boot und Rollback spielen den gespeicherten Graphen wieder ab, statt neu aufzulösen, sodass eine Version stets mit den Versionen abgeglichen wird, mit denen sie aufgelöst wurde. Das Historie-Schema migriert automatisch beim ersten Boot nach einem Upgrade; eine bereits vorhandene Version wird beim ersten Besuch einmal aufgelöst und als Checkpoint gespeichert.
Navigation
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.
Finder
Query-Engine mit LRU-Caching für Entry-Suche:
| Operator | Präfix | Beispiel |
|---|---|---|
| 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.