feat: RegisterService with instance storage + interface discovery
Restores v0.3.3 service manager capabilities: - RegisterService(name, instance) stores the raw instance - Auto-discovers Startable/Stoppable interfaces → wires lifecycle - Auto-discovers HandleIPCEvents → wires to IPC bus - ServiceFor[T](c, name) for typed instance retrieval - Service DTO gains Instance field for instance tracking WithService is a simple factory call — no reflect, no magic. discoverHandlers removed — RegisterService handles it inline. No double-registration: IPC wired once at registration time. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
64e6a26ea8
commit
74f78c83a2
3 changed files with 80 additions and 37 deletions
10
contract.go
10
contract.go
|
|
@ -112,10 +112,6 @@ func New(opts ...CoreOption) Result {
|
|||
}
|
||||
}
|
||||
|
||||
// Post-construction: discover IPC handlers on all registered services.
|
||||
// Services that implement HandleIPCEvents get auto-wired to the bus.
|
||||
c.discoverHandlers()
|
||||
|
||||
return Result{c, true}
|
||||
}
|
||||
|
||||
|
|
@ -135,10 +131,8 @@ func WithOptions(opts Options) CoreOption {
|
|||
}
|
||||
|
||||
// WithService registers a service via its factory function.
|
||||
// The factory receives *Core so the service can wire IPC handlers
|
||||
// and access other subsystems during construction.
|
||||
// Service name is auto-discovered from the package path.
|
||||
// If the service implements HandleIPCEvents, it is auto-registered.
|
||||
// The factory receives *Core and is responsible for calling c.Service()
|
||||
// to register itself, and c.RegisterAction() for IPC handlers.
|
||||
//
|
||||
// core.WithService(agentic.Register)
|
||||
// core.WithService(display.Register(nil))
|
||||
|
|
|
|||
25
core.go
25
core.go
|
|
@ -7,7 +7,6 @@ package core
|
|||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
|
@ -81,28 +80,4 @@ func (c *Core) Must(err error, op, msg string) {
|
|||
c.log.Must(err, op, msg)
|
||||
}
|
||||
|
||||
// --- Post-Construction ---
|
||||
|
||||
// discoverHandlers scans Config for service instances that implement HandleIPCEvents.
|
||||
// Called once after all WithService options have run — services are fully registered.
|
||||
func (c *Core) discoverHandlers() {
|
||||
if c.config == nil || c.config.ConfigOptions == nil || c.config.Settings == nil {
|
||||
return
|
||||
}
|
||||
c.config.mu.RLock()
|
||||
defer c.config.mu.RUnlock()
|
||||
for _, val := range c.config.Settings {
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
instanceValue := reflect.ValueOf(val)
|
||||
handlerMethod := instanceValue.MethodByName("HandleIPCEvents")
|
||||
if handlerMethod.IsValid() {
|
||||
if handler, ok := handlerMethod.Interface().(func(*Core, Message) Result); ok {
|
||||
c.RegisterAction(handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Global Instance ---
|
||||
|
|
|
|||
82
service.go
82
service.go
|
|
@ -2,9 +2,13 @@
|
|||
|
||||
// Service registry for the Core framework.
|
||||
//
|
||||
// Register a service:
|
||||
// Register a service (DTO with lifecycle hooks):
|
||||
//
|
||||
// c.Service("auth", core.Service{})
|
||||
// c.Service("auth", core.Service{OnStart: startFn})
|
||||
//
|
||||
// Register a service instance (auto-discovers Startable/Stoppable/HandleIPCEvents):
|
||||
//
|
||||
// c.RegisterService("display", displayInstance)
|
||||
//
|
||||
// Get a service:
|
||||
//
|
||||
|
|
@ -13,11 +17,10 @@
|
|||
|
||||
package core
|
||||
|
||||
// No imports needed — uses package-level string helpers.
|
||||
|
||||
// Service is a managed component with optional lifecycle.
|
||||
type Service struct {
|
||||
Name string
|
||||
Instance any // the raw service instance (for interface discovery)
|
||||
Options Options
|
||||
OnStart func() Result
|
||||
OnStop func() Result
|
||||
|
|
@ -66,6 +69,77 @@ func (c *Core) Service(name string, service ...Service) Result {
|
|||
return Result{OK: true}
|
||||
}
|
||||
|
||||
// RegisterService registers a service instance by name.
|
||||
// Auto-discovers Startable, Stoppable, and HandleIPCEvents interfaces
|
||||
// on the instance and wires them into the lifecycle and IPC bus.
|
||||
//
|
||||
// c.RegisterService("display", displayInstance)
|
||||
func (c *Core) RegisterService(name string, instance any) Result {
|
||||
if name == "" {
|
||||
return Result{E("core.RegisterService", "service name cannot be empty", nil), false}
|
||||
}
|
||||
|
||||
c.Lock("srv").Mutex.Lock()
|
||||
defer c.Lock("srv").Mutex.Unlock()
|
||||
|
||||
if c.services.locked {
|
||||
return Result{E("core.RegisterService", Concat("service \"", name, "\" not permitted — registry locked"), nil), false}
|
||||
}
|
||||
if _, exists := c.services.services[name]; exists {
|
||||
return Result{E("core.RegisterService", Join(" ", "service", name, "already registered"), nil), false}
|
||||
}
|
||||
|
||||
srv := &Service{Name: name, Instance: instance}
|
||||
|
||||
// Auto-discover lifecycle interfaces
|
||||
if s, ok := instance.(Startable); ok {
|
||||
srv.OnStart = func() Result {
|
||||
if err := s.OnStartup(c.context); err != nil {
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
}
|
||||
}
|
||||
if s, ok := instance.(Stoppable); ok {
|
||||
srv.OnStop = func() Result {
|
||||
if err := s.OnShutdown(c.context); err != nil {
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
}
|
||||
}
|
||||
|
||||
c.services.services[name] = srv
|
||||
|
||||
// Auto-discover IPC handler
|
||||
if handler, ok := instance.(interface {
|
||||
HandleIPCEvents(*Core, Message) Result
|
||||
}); ok {
|
||||
c.ipc.ipcMu.Lock()
|
||||
c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handler.HandleIPCEvents)
|
||||
c.ipc.ipcMu.Unlock()
|
||||
}
|
||||
|
||||
return Result{OK: true}
|
||||
}
|
||||
|
||||
// ServiceFor retrieves a registered service by name and asserts its type.
|
||||
//
|
||||
// prep, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic")
|
||||
func ServiceFor[T any](c *Core, name string) (T, bool) {
|
||||
var zero T
|
||||
r := c.Service(name)
|
||||
if !r.OK {
|
||||
return zero, false
|
||||
}
|
||||
svc := r.Value.(*Service)
|
||||
if svc.Instance == nil {
|
||||
return zero, false
|
||||
}
|
||||
typed, ok := svc.Instance.(T)
|
||||
return typed, ok
|
||||
}
|
||||
|
||||
// Services returns all registered service names.
|
||||
//
|
||||
// names := c.Services()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue