4.5 KiB
| title | description |
|---|---|
| Getting Started | How to create a Core application and register services. |
Getting Started
This guide walks you through creating a Core application, registering services, and running the lifecycle.
Installation
go get forge.lthn.ai/core/go
Creating a Core Instance
Everything starts with core.New(). It accepts a variadic list of Option functions that configure the container before it is returned.
package main
import "forge.lthn.ai/core/go/pkg/core"
func main() {
c, err := core.New()
if err != nil {
panic(err)
}
_ = c // empty container, ready for use
}
In practice you will pass options to register services, embed assets, or lock the registry:
c, err := core.New(
core.WithService(mypackage.NewService),
core.WithAssets(embeddedFS),
core.WithServiceLock(),
)
See Configuration for the full list of options.
Registering a Service
Services are registered via factory functions. A factory receives the *Core and returns (any, error):
package greeter
import "forge.lthn.ai/core/go/pkg/core"
type Service struct {
greeting string
}
func (s *Service) Hello(name string) string {
return s.greeting + ", " + name + "!"
}
func NewService(c *core.Core) (any, error) {
return &Service{greeting: "Hello"}, nil
}
Register it with WithService:
c, err := core.New(
core.WithService(greeter.NewService),
)
WithService automatically discovers the service name from the package path. In this case, the service is registered under the name "greeter".
If you need to control the name explicitly, use WithName:
c, err := core.New(
core.WithName("greet", greeter.NewService),
)
See Services for the full registration API and the ServiceRuntime helper.
Retrieving a Service
Once registered, services can be retrieved by name:
// Untyped retrieval (returns any)
svc := c.Service("greeter")
// Type-safe retrieval (returns error if not found or wrong type)
greet, err := core.ServiceFor[*greeter.Service](c, "greeter")
// Panicking retrieval (for init-time wiring where failure is fatal)
greet := core.MustServiceFor[*greeter.Service](c, "greeter")
Running the Lifecycle
Services that implement Startable and/or Stoppable are automatically called during startup and shutdown:
import "context"
// Start all Startable services (in registration order)
err := c.ServiceStartup(context.Background(), nil)
// ... application runs ...
// Stop all Stoppable services (in reverse registration order)
err = c.ServiceShutdown(context.Background())
See Lifecycle for details on the Startable and Stoppable interfaces.
Sending Messages
Services communicate through the message bus without needing direct imports of each other:
// Broadcast to all handlers (fire-and-forget)
err := c.ACTION(MyEvent{Data: "something happened"})
// Request data from the first handler that responds
result, handled, err := c.QUERY(MyQuery{Key: "setting"})
// Ask a handler to perform work
result, handled, err := c.PERFORM(MyTask{Input: "data"})
See Messaging for the full message bus API.
Putting It All Together
Here is a minimal but complete application:
package main
import (
"context"
"fmt"
"forge.lthn.ai/core/go/pkg/core"
"forge.lthn.ai/core/go/pkg/log"
)
func main() {
c, err := core.New(
core.WithName("log", log.NewService(log.Options{Level: log.LevelInfo})),
core.WithServiceLock(),
)
if err != nil {
panic(err)
}
// Start lifecycle
if err := c.ServiceStartup(context.Background(), nil); err != nil {
panic(err)
}
// Use services
logger := core.MustServiceFor[*log.Service](c, "log")
fmt.Println("Logger started at level:", logger.Level())
// Query the log level through the message bus
level, handled, _ := c.QUERY(log.QueryLevel{})
if handled {
fmt.Println("Log level via QUERY:", level)
}
// Clean shutdown
if err := c.ServiceShutdown(context.Background()); err != nil {
fmt.Println("shutdown error:", err)
}
}