Skip to content

Entry Handlers

Entry handlers process registry entries by kind. When entries are added, updated, or deleted, the registry dispatches events to matching handlers.

The registry maintains a map of kind patterns to handlers. When an entry changes:

  1. Registry emits event (entry.create, entry.update, entry.delete)
  2. Handler registry matches entry kind against registered patterns
  3. Matching handlers receive the entry
  4. Handlers process or reject the entry

Handlers subscribe using patterns:

PatternMatches
http.serviceExact match only
http.*http.service, http.router, http.endpoint
function.**function.lua, function.lua.bc

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 rejects the entry.

TypePurposeCan Reject
ListenerPrimary handlerYes
ObserverSecondary handler (logging, metrics)No
handlers.RegisterListener("http.*", httpManager)
handlers.RegisterObserver("function.*", metricsCollector)

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
},
})
}

Use entry.DecodeEntryConfig from internal/entry to unmarshal entry data. This helper lives under internal/, so it is only importable from inside the runtime module; out-of-tree extensions must copy the pattern or use the transcoder directly:

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:

  1. Unmarshals entry.Data into your config struct
  2. Populates ID and Meta from the entry
  3. Calls InitDefaults() if implemented
  4. Calls Validate() if implemented

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
}

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.