# How to Register and Manage Services Using serv.RegisterService in Gorig

> Learn to register and manage services using serv RegisterService in Gorig. Start your registered services and ensure graceful shutdown easily.

- Repository: [Jom/gorig](https://github.com/jom-io/gorig)
- Tags: how-to-guide
- Published: 2026-03-05

---

**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](https://github.com/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`](https://github.com/jom-io/gorig/blob/main/serv/serv.go):

```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:

```go
func RegisterService(service ...Service) *errors.Error

```

Registration should occur during the application bootstrap phase, typically in [`bootstrap/startup.go`](https://github.com/jom-io/gorig/blob/main/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`](https://github.com/jom-io/gorig/blob/main/bootstrap/startup.go) demonstrates how Gorig registers its default HTTP server using `httpx` package implementations:

```go
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:

```go
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:

```go
// 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:

```go
// 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:

```go
// 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`](https://github.com/jom-io/gorig/blob/main/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:

```go
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.Service` struct with unique `Code`, optional `PORT`, and `Startup`/`Shutdown` functions.
- **Registration**: Call `serv.RegisterService()` during bootstrap to add services to the global `gServices` registry; 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 `Shutdown` functions to respect context cancellation, ensuring services terminate cleanly within the timeout window managed by `serv.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`](https://github.com/jom-io/gorig/blob/main/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`](https://github.com/jom-io/gorig/blob/main/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.