go/docs/services.md
Snider 89d189dd95 docs: add human-friendly documentation for Core Go framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:02:37 +00:00

215 lines
6 KiB
Markdown

---
title: Services
description: Service registration, retrieval, ServiceRuntime, and factory patterns.
---
# Services
Services are the building blocks of a Core application. They are plain Go structs registered into a named registry and retrieved by name with optional type assertions.
## Registration
### Factory Functions
The primary way to register a service is via a **factory function** -- a function with the signature `func(*Core) (any, error)`. The factory receives the `Core` instance so it can access other services or register message handlers during construction.
```go
func NewMyService(c *core.Core) (any, error) {
return &MyService{}, nil
}
```
### WithService (auto-named)
`WithService` registers a service and automatically discovers its name from the Go package path. The last segment of the package path becomes the service name, lowercased.
```go
// If MyService lives in package "myapp/services/calculator",
// it is registered as "calculator".
c, err := core.New(
core.WithService(calculator.NewService),
)
```
`WithService` also performs **IPC handler discovery**: if the returned service has a method named `HandleIPCEvents` with the signature `func(*Core, Message) error`, it is automatically registered as an action handler.
```go
type Service struct{}
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
// Handle messages
return nil
}
```
### WithName (explicitly named)
When you need to control the service name (or the factory is an anonymous function), use `WithName`:
```go
c, err := core.New(
core.WithName("my-service", func(c *core.Core) (any, error) {
return &MyService{}, nil
}),
)
```
Unlike `WithService`, `WithName` does **not** auto-discover IPC handlers. Register them manually if needed.
### Direct Registration
You can also register a service directly on an existing `Core` instance:
```go
err := c.RegisterService("my-service", &MyService{})
```
This is useful for tests or when constructing services outside the `New()` options flow.
### Registration Rules
- Service names **must not be empty**.
- **Duplicate names** are rejected with an error.
- If `WithServiceLock()` was passed to `New()`, registration after initialisation is rejected.
## Retrieval
### By Name (untyped)
```go
svc := c.Service("calculator")
if svc == nil {
// not found
}
```
Returns `nil` if no service is registered under that name.
### Type-Safe Retrieval
`ServiceFor[T]` retrieves and type-asserts in one step:
```go
calc, err := core.ServiceFor[*calculator.Service](c, "calculator")
if err != nil {
// "service 'calculator' not found"
// or "service 'calculator' is of type *Foo, but expected *calculator.Service"
}
```
### Panicking Retrieval
For init-time wiring where a missing service is a fatal programming error:
```go
calc := core.MustServiceFor[*calculator.Service](c, "calculator")
// panics if not found or wrong type
```
## ServiceRuntime
`ServiceRuntime[T]` is a generic helper you embed in your service struct. It provides typed access to the `Core` instance and your service's options struct.
```go
type Options struct {
Precision int
}
type Service struct {
*core.ServiceRuntime[Options]
}
func NewService(opts Options) func(*core.Core) (any, error) {
return func(c *core.Core) (any, error) {
return &Service{
ServiceRuntime: core.NewServiceRuntime(c, opts),
}, nil
}
}
```
`ServiceRuntime` provides these methods:
| Method | Returns | Description |
|--------|---------|-------------|
| `Core()` | `*Core` | The central Core instance |
| `Opts()` | `T` | The service's typed options |
| `Config()` | `Config` | Convenience shortcut for `Core().Config()` |
### Real-World Example: The Log Service
The `pkg/log` package in this repository is the reference implementation of a Core service:
```go
type Service struct {
*core.ServiceRuntime[Options]
*Logger
}
func NewService(opts Options) func(*core.Core) (any, error) {
return func(c *core.Core) (any, error) {
logger := New(opts)
return &Service{
ServiceRuntime: core.NewServiceRuntime(c, opts),
Logger: logger,
}, nil
}
}
func (s *Service) OnStartup(ctx context.Context) error {
s.Core().RegisterQuery(s.handleQuery)
s.Core().RegisterTask(s.handleTask)
return nil
}
```
Key patterns to note:
1. The factory is a **closure** -- `NewService` takes options and returns a factory function.
2. `ServiceRuntime` is embedded, giving access to `Core()` and `Opts()`.
3. The service implements `Startable` to register its query/task handlers at startup.
## Runtime and NewWithFactories
For applications that wire services from a map of named factories, `NewWithFactories` offers a bulk registration path:
```go
type ServiceFactory func() (any, error)
rt, err := core.NewWithFactories(app, map[string]core.ServiceFactory{
"config": configFactory,
"database": dbFactory,
"cache": cacheFactory,
})
```
Factories are called in sorted key order. The resulting `Runtime` wraps a `Core` and exposes `ServiceStartup`/`ServiceShutdown` for GUI runtime integration.
For the simplest case with no custom services:
```go
rt, err := core.NewRuntime(app)
```
## Well-Known Services
Core provides convenience methods for commonly needed services. These use `MustServiceFor` internally and will panic if the service is not registered:
| Method | Expected Name | Expected Interface |
|--------|--------------|-------------------|
| `c.Config()` | `"config"` | `Config` |
| `c.Display()` | `"display"` | `Display` |
| `c.Workspace()` | `"workspace"` | `Workspace` |
| `c.Crypt()` | `"crypt"` | `Crypt` |
These are optional -- only call them if you have registered the corresponding service.
## Thread Safety
The service registry is protected by `sync.RWMutex`. Registration, retrieval, and lifecycle operations are safe to call from multiple goroutines.
## Related Pages
- [Lifecycle](lifecycle.md) -- `Startable` and `Stoppable` interfaces
- [Messaging](messaging.md) -- how services communicate
- [Configuration](configuration.md) -- all `With*` options