How to Register and Manage Services Using serv.RegisterService in Gorig
Use serv.RegisterService to register one or more Service structs during application bootstrap, then invoke serv.Running() to start all registered services and handle graceful shutdown.
The serv package in the jom-io/gorig repository provides a centralized service lifecycle manager for Go applications. By using serv.RegisterService, you can decouple service definition from execution, allowing HTTP servers, background workers, and custom processes to register themselves during initialization and start uniformly under a single control loop.
Understanding the Service Architecture in Gorig
At the core of Gorig's service management is the Service struct defined in serv/serv.go:
type Service struct {
Code string // unique identifier
PORT string // optional listening port
Startup func(code, port string) error // called when the service starts
Shutdown func(code string, ctx context.Context) error // called on graceful stop
}
All registered services are stored in a private global map gServices (map[string]Service). The RegisterService function safely inserts one or more Service instances into this map, rejecting duplicates by checking against existing codes via the internal doRegisterService helper.
How to Register a Service with serv.RegisterService
The RegisterService function accepts variadic Service structs and returns an error if registration fails:
func RegisterService(service ...Service) *errors.Error
Registration should occur during the application bootstrap phase, typically in bootstrap/startup.go or an equivalent initialization file. Each service must have a unique Code to prevent collisions in the global registry.
Registering the Built-in HTTP Service
The following example from bootstrap/startup.go demonstrates how Gorig registers its default HTTP server using httpx package implementations:
func regWebService() {
err := serv.RegisterService(serv.Service{
Code: "HTTP",
PORT: configure.GetString("api.rest.addr", ":9617"),
Startup: httpx.Startup, // func(string,string) error
Shutdown: httpx.Shutdown, // func(string,context.Context) error
})
if err != nil {
sys.Exit(err) // abort if registration fails
}
}
This registration pulls the port from configuration and delegates the actual server logic to the httpx package, maintaining clean separation between the service registry and implementation.
Registering a Custom Background Worker
For background processes that do not bind to a port, omit the PORT field and implement custom Startup and Shutdown logic:
package workers
import (
"context"
"time"
"github.com/jom-io/gorig/serv"
"github.com/jom-io/gorig/utils/sys"
)
func runWorker(ctx context.Context) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
sys.Info("processing background task")
case <-ctx.Done():
sys.Info("worker received shutdown signal")
return
}
}
}
func startup(code, _ string) error {
go runWorker(context.Background())
sys.Success("worker service started")
return nil
}
func shutdown(_ string, ctx context.Context) error {
// Context cancellation handled in runWorker via ctx.Done()
return nil
}
func RegisterWorker() {
if err := serv.RegisterService(serv.Service{
Code: "WORKER",
Startup: startup,
Shutdown: shutdown,
}); err != nil {
sys.Exit(err)
}
}
This pattern allows long-running goroutines to participate in the unified lifecycle managed by the serv package.
Managing the Service Lifecycle
Once services are registered, the serv package provides three primary mechanisms for lifecycle control: bulk startup, selective startup, and graceful shutdown.
Starting All Services
The Running() function initiates the main application loop. It iterates over gServices, invokes each Startup function, and blocks until an interrupt signal is received:
// In bootstrap/startup.go or main.go
serv.Running()
Under the hood, Running() handles OS signals (SIGINT, SIGTERM) and coordinates the shutdown sequence when the process needs to terminate.
Selective Service Startup with StartCode
For scenarios requiring dynamic or conditional service startup, use StartCode(code string) to launch a specific service by its registered code:
// Start only the background worker
if err := serv.StartCode("WORKER"); err != nil {
sys.Error("failed to start worker:", err)
}
This is useful for CLI tools that run specific services independently or for testing individual components without starting the entire application stack.
Graceful Shutdown Handling
When Running() receives an interrupt signal, it creates a timeout context (default 5 seconds) and invokes each service's Shutdown function in reverse registration order:
// Shutdown signature implemented by services
func Shutdown(code string, ctx context.Context) error
Services should respect the context cancellation to ensure timely termination. The HTTP service in httpx/http.go demonstrates this by calling server.Shutdown(ctx) within its shutdown handler.
Complete Bootstrap Example
The following initialization sequence demonstrates registering multiple service types and launching the application:
package bootstrap
import (
"github.com/jom-io/gorig/serv"
"github.com/jom-io/gorig/httpx"
"github.com/jom-io/gorig/workers"
"github.com/jom-io/gorig/utils/configure"
"github.com/jom-io/gorig/utils/sys"
)
func StartUp() {
// Register HTTP API service
err := serv.RegisterService(serv.Service{
Code: "HTTP",
PORT: configure.GetString("api.rest.addr", ":9617"),
Startup: httpx.Startup,
Shutdown: httpx.Shutdown,
})
if err != nil {
sys.Exit(err)
}
// Register background worker
workers.RegisterWorker()
// Start all registered services and block until shutdown
serv.Running()
}
This pattern scales to any number of services while maintaining clean separation between business logic and lifecycle management.
Summary
- Service Definition: Create a
serv.Servicestruct with uniqueCode, optionalPORT, andStartup/Shutdownfunctions. - Registration: Call
serv.RegisterService()during bootstrap to add services to the globalgServicesregistry; duplicates are rejected automatically. - Bulk Startup: Invoke
serv.Running()to start all registered services and block until an interrupt signal triggers graceful shutdown. - Selective Startup: Use
serv.StartCode("CODE")to launch individual services dynamically without affecting others. - Graceful Shutdown: Implement
Shutdownfunctions to respect context cancellation, ensuring services terminate cleanly within the timeout window managed byserv.Running().
Frequently Asked Questions
What happens if I try to register two services with the same Code?
The RegisterService function returns an error if a service with the specified Code already exists in the gServices map. According to the implementation in serv/serv.go, the internal doRegisterService helper checks for existing entries before insertion, preventing duplicate registrations and ensuring each service maintains a unique identifier.
Can I register a service without a Shutdown function?
While the Service struct allows nil functions, it is strongly recommended to provide a Shutdown implementation for any service that starts background goroutines or holds resources. The serv.Running() function invokes Shutdown for every registered service during graceful termination, passing a timeout context. If Shutdown is nil or unimplemented, the service may not terminate cleanly, potentially causing goroutine leaks or resource exhaustion.
How do I start only specific services for testing?
Use the serv.StartCode(code string) function to launch individual services by their registered identifier. This method bypasses the bulk startup logic in serv.Running() and invokes only the specified service's Startup function. For testing scenarios, you can register multiple services but selectively start only the component under test, leaving others dormant until explicitly needed.
Where should I place the RegisterService calls in my application?
Place RegisterService calls in your bootstrap or initialization package, typically in a file like bootstrap/startup.go. This ensures services are registered before serv.Running() is invoked in your main function. The registration phase should occur after configuration loading but before the application enters its runtime loop, ensuring all service codes are validated and the global registry is fully populated before startup begins.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →