2 Service-Runtime
Snider edited this page 2026-03-14 13:43:31 +00:00

Service Runtime

ServiceRuntime[T] is a generic helper that provides type-safe options and Core access to services. Embed it in your service struct to avoid boilerplate.

Usage

type Options struct {
    MaxRetries int
    ConfigPath string
}

type Service struct {
    *core.ServiceRuntime[Options]
}

func NewService(c *core.Core) (any, error) {
    opts := Options{MaxRetries: 3, ConfigPath: "/etc/app"}
    return &Service{
        ServiceRuntime: core.NewServiceRuntime(c, opts),
    }, nil
}

Methods

Method Returns Purpose
Core() *Core Access the Core instance
Opts() T Retrieve type-safe options
Config() Config Convenience accessor for config service

Full Service Example

package myservice

import (
    "context"
    core "forge.lthn.ai/core/go/pkg/core"
)

type Options struct {
    MaxRetries int
}

type Service struct {
    *core.ServiceRuntime[Options]
}

func NewService(c *core.Core) (any, error) {
    return &Service{
        ServiceRuntime: core.NewServiceRuntime(c, Options{MaxRetries: 3}),
    }, nil
}

// Lifecycle
func (s *Service) OnStartup(ctx context.Context) error {
    return nil
}

func (s *Service) OnShutdown(ctx context.Context) error {
    return nil
}

// IPC (auto-registered by WithService)
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
    return nil
}

Error Helper

The E() function creates contextual errors:

return core.E("config.Load", "failed to read config file", err)
// Output: "config.Load: failed to read config file: open /etc/app.yml: no such file"

The Error struct implements Unwrap() for Go's error chaining (errors.Is, errors.As).

Test Conventions

Tests follow the Good/Bad/Ugly suffix pattern:

func TestService_New_Good(t *testing.T)   { /* Happy path */ }
func TestService_New_Bad(t *testing.T)    { /* Expected errors */ }
func TestService_New_Ugly(t *testing.T)   { /* Panics, edge cases */ }

See Service-Lifecycle for registration patterns and Message-Bus for ACTION/QUERY/TASK.