Entry Listeners and Observers
Entry listeners and observers process registry mutations for matching entry-kind patterns.
This is a Go extension reference. The registration and configuration snippets assume an existing boot component, manager, transcoder, and application config type.
How It Works
Boot collects listeners and observers with their kind patterns. When an entry changes:
- Registry emits event (
entry.create,entry.update,entry.delete) - Each listener wrapper matches the entry kind against its registered pattern
- Matching handlers receive the entry
- Handlers process or reject the entry
Kind Patterns
Handlers subscribe using patterns:
| Pattern | Matches |
|---|---|
http.service |
Exact match only |
http.* |
http.service, http.router, http.endpoint |
function.** |
function.lua, function.lua.bc |
Entry Listener Interface
Handlers implement registry.EntryListener:
type EntryListener interface {
Add(ctx context.Context, entry Entry) error
Update(ctx context.Context, entry Entry) error
Delete(ctx context.Context, entry Entry) error
}
Returning an error from Add, Update, or Delete rejects that operation.
Listener vs Observer
| Type | Purpose | Can Reject |
|---|---|---|
| Listener | Primary handler | Yes |
| Observer | Secondary handler (logging, metrics) | No |
handlers.RegisterListener("http.*", httpManager)
handlers.RegisterObserver("function.*", metricsCollector)
Observer errors from Add, Update, and Delete are ignored and do not emit an accept or reject event. A listener or observer that also implements TransactionListener participates in transaction barriers, where an error from Begin, Commit, or Discard rejects that transaction phase.
Registering Handlers
Register handlers during boot:
func MyService() boot.Component {
return boot.New(boot.P{
Name: "myservice",
DependsOn: []boot.Name{core.RegistryName},
Load: func(ctx context.Context) (context.Context, error) {
handlers := bootpkg.GetHandlerRegistry(ctx)
handlers.RegisterListener("myservice.*", manager)
return ctx, nil
},
})
}
Decoding Entry Data
Use entry.DecodeEntryConfig from system/entry to unmarshal entry data. DecodeEntryConfigFromContext takes the transcoder from the context instead of an argument, and DecodeEntryConfigRaw skips placeholder resolution:
func (m *Manager) Add(ctx context.Context, ent registry.Entry) error {
cfg, err := entry.DecodeEntryConfig[ComponentConfig](ctx, m.dtt, ent)
if err != nil {
return err
}
// Process cfg...
return nil
}
The decoder:
- Resolves
${env:...}placeholders and*_envcompanion fields against the environment registry - Unmarshals
entry.Datainto your config struct - Populates
IDandMetafrom the entry when the struct leaves them empty - Calls
InitDefaults()if implemented - Calls
Validate()if implemented
Config Structure
Entry configs typically include:
type ComponentConfig struct {
ID registry.ID `json:"id"`
Meta attrs.Bag `json:"meta"`
Name string `json:"name"`
Timeout int `json:"timeout,omitempty"`
}
func (c *ComponentConfig) InitDefaults() {
if c.Timeout == 0 {
c.Timeout = 30
}
}
func (c *ComponentConfig) Validate() error {
if c.Name == "" {
return fmt.Errorf("name is required")
}
return nil
}
Transaction Support
For atomic operations across multiple entries, implement TransactionListener:
type TransactionListener interface {
Begin(ctx context.Context) error
Commit(ctx context.Context) error
Discard(ctx context.Context) error
}
The registry calls Begin before processing a batch, then Commit on success or Discard on failure.
See Also
- Registry - Entry storage
- Architecture - Boot sequence