go/docs/services.md
Snider 2d52b83f60 docs: rewrite documentation suite against AX spec
Codex-authored docs covering primitives, commands, messaging,
lifecycle, subsystems, and getting started — all using the current
DTO/Options/Result API with concrete usage examples.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 10:05:04 +00:00

152 lines
3.2 KiB
Markdown

---
title: Services
description: Register, inspect, and lock CoreGO services.
---
# Services
In CoreGO, a service is a named lifecycle entry stored in the Core registry.
## Register a Service
```go
c := core.New()
r := c.Service("audit", core.Service{
OnStart: func() core.Result {
core.Info("audit started")
return core.Result{OK: true}
},
OnStop: func() core.Result {
core.Info("audit stopped")
return core.Result{OK: true}
},
})
```
Registration succeeds when:
- the name is not empty
- the registry is not locked
- the name is not already in use
## Read a Service Back
```go
r := c.Service("audit")
if r.OK {
svc := r.Value.(*core.Service)
_ = svc
}
```
The returned value is `*core.Service`.
## List Registered Services
```go
names := c.Services()
```
### Important Detail
The current registry is map-backed. `Services()`, `Startables()`, and `Stoppables()` do not promise a stable order.
## Lifecycle Snapshots
Use these helpers when you want the current set of startable or stoppable services:
```go
startables := c.Startables()
stoppables := c.Stoppables()
```
They return `[]*core.Service` inside `Result.Value`.
## Lock the Registry
CoreGO has a service-lock mechanism, but it is explicit.
```go
c := core.New()
c.LockEnable()
c.Service("audit", core.Service{})
c.Service("cache", core.Service{})
c.LockApply()
```
After `LockApply`, new registrations fail:
```go
r := c.Service("late", core.Service{})
fmt.Println(r.OK) // false
```
The default lock name is `"srv"`. You can pass a different name if you need a custom lock namespace.
For the service registry itself, use the default `"srv"` lock path. That is the path used by `Core.Service(...)`.
## `NewWithFactories`
For GUI runtimes or factory-driven setup, CoreGO provides `NewWithFactories`.
```go
r := core.NewWithFactories(nil, map[string]core.ServiceFactory{
"audit": func() core.Result {
return core.Result{Value: core.Service{
OnStart: func() core.Result {
return core.Result{OK: true}
},
}, OK: true}
},
"cache": func() core.Result {
return core.Result{Value: core.Service{}, OK: true}
},
})
```
### Important Details
- each factory must return a `core.Service` in `Result.Value`
- factories are executed in sorted key order
- nil factories are skipped
- the return value is `*core.Runtime`
## `Runtime`
`Runtime` is a small wrapper used for external runtimes such as GUI bindings.
```go
r := core.NewRuntime(nil)
rt := r.Value.(*core.Runtime)
_ = rt.ServiceStartup(context.Background(), nil)
_ = rt.ServiceShutdown(context.Background())
```
`Runtime.ServiceName()` returns `"Core"`.
## `ServiceRuntime[T]` for Package Authors
If you are writing a package on top of CoreGO, use `ServiceRuntime[T]` to keep a typed options struct and the parent `Core` together.
```go
type repositoryServiceOptions struct {
BaseDirectory string
}
type repositoryService struct {
*core.ServiceRuntime[repositoryServiceOptions]
}
func newRepositoryService(c *core.Core) *repositoryService {
return &repositoryService{
ServiceRuntime: core.NewServiceRuntime(c, repositoryServiceOptions{
BaseDirectory: "/srv/repos",
}),
}
}
```
This is a package-authoring helper. It does not replace the `core.Service` registry entry.