lokstra_registry

Simplified API for the Lokstra registry system

Overview

The lokstra_registry package provides a clean, package-level API for registering and accessing services, middleware, routers, and configuration. It wraps deploy.GlobalRegistry() to provide:

Import Path

import "github.com/primadi/lokstra/lokstra_registry"

Service Registration

RegisterServiceType

Registers a service factory with optional metadata for auto-router generation.

Signature:

func RegisterServiceType(
    serviceType string,
    local, remote any,
    options ...deploy.RegisterServiceTypeOption,
)

Parameters:

Factory Signatures (All Auto-Wrapped):

// Simplest - no dependencies, no config
func() any

// With config
func(cfg map[string]any) any

// Full control - with dependencies and config
func(deps, cfg map[string]any) any

Example:

// Simple factory (no deps, no config)
lokstra_registry.RegisterServiceType("user-service",
    func() any {
        return &UserService{}
    },
    nil, // No remote implementation
    deploy.WithResource("user", "users"),
)

// With config
lokstra_registry.RegisterServiceType("db-service",
    func(cfg map[string]any) any {
        dsn := cfg["dsn"].(string)
        return db.NewConnection(dsn)
    },
    nil,
)

// Full signature with deps
lokstra_registry.RegisterServiceType("order-service",
    func(deps, cfg map[string]any) any {
        userSvc := deps["userService"].(*service.Cached[*UserService])
        maxItems := cfg["max_items"].(int)
        return &OrderService{
            userService: userSvc,
            maxItems:    maxItems,
        }
    },
    nil,
    deploy.WithDependencies("userService"),
)

// With remote implementation
lokstra_registry.RegisterServiceType("payment-service",
    // Local implementation
    func(deps, cfg map[string]any) any {
        return &PaymentService{db: deps["db"].(*DBService)}
    },
    // Remote implementation (API client)
    func(deps, cfg map[string]any) any {
        proxy := deps["remote"].(*proxy.Service)
        return &PaymentServiceRemote{proxy: proxy}
    },
    deploy.WithResource("payment", "payments"),
    deploy.WithDependencies("db"),
)

Options:

deploy.WithResource(singular, plural string)
deploy.WithConvention(convention string)
deploy.WithDependencies(deps ...string)
deploy.WithPathPrefix(prefix string)
deploy.WithMiddleware(names ...string)
deploy.WithRouteOverride(methodName, path string)
deploy.WithHiddenMethods(methods ...string)

DefineService

Defines a service instance in the global registry (code-based config).

Signature:

func DefineService(def *schema.ServiceDef)

Example:

lokstra_registry.DefineService(&schema.ServiceDef{
    Name: "user-service",
    Type: "user-service-factory",
    Config: map[string]any{
        "max_connections": 10,
    },
    DependsOn: []string{"db-service"},
})

Use Cases:


RegisterLazyService

Registers a lazy service that will be instantiated on first access.

Signature:

func RegisterLazyService(name string, factory any, config map[string]any)

Factory Signatures:

func() any                    // No params (simplest!)
func(cfg map[string]any) any  // With config

Benefits:

Example:

// With config
lokstra_registry.RegisterLazyService("db-main", func(cfg map[string]any) any {
    dsn := cfg["dsn"].(string)
    return db.NewConnection(dsn)
}, map[string]any{
    "dsn": "postgresql://localhost/main",
})

// Without params (resolve deps manually)
lokstra_registry.RegisterLazyService("user-repo", func() any {
    db := lokstra_registry.MustGetService[*DB]("db-main")
    return repository.NewUserRepository(db)
}, nil)

RegisterLazyServiceWithDeps

Registers a lazy service with explicit dependency injection.

Signature:

func RegisterLazyServiceWithDeps(
    name string,
    factory any,
    deps map[string]string,
    config map[string]any,
    opts ...deploy.LazyServiceOption,
)

Parameters:

Example:

lokstra_registry.RegisterLazyServiceWithDeps("order-service",
    func(deps, cfg map[string]any) any {
        // deps already contains resolved services!
        userSvc := deps["userService"].(*UserService)
        orderRepo := deps["orderRepo"].(*OrderRepository)
        maxItems := cfg["max_items"].(int)
        return &OrderService{
            userService: userSvc,
            orderRepo:   orderRepo,
            maxItems:    maxItems,
        }
    },
    map[string]string{
        "userService": "user-service",
        "orderRepo":   "order-repo",
    },
    map[string]any{"max_items": 5},
)

// Skip if already registered
lokstra_registry.RegisterLazyServiceWithDeps(name, factory, deps, cfg,
    deploy.WithRegistrationMode(deploy.LazyServiceSkip))

// Override existing registration
lokstra_registry.RegisterLazyServiceWithDeps(name, factory, deps, cfg,
    deploy.WithRegistrationMode(deploy.LazyServiceOverride))

Service Access

GetService

Retrieves a service with type assertion (generic).

Signature:

func GetService[T any](name string) T

Returns:

Example:

userSvc := lokstra_registry.GetService[*UserService]("user-service")
if userSvc != nil {
    users := userSvc.GetAll()
}

MustGetService

Retrieves a service with type assertion (panics if not found).

Signature:

func MustGetService[T any](name string) T

Returns:

Panics:

Example:

userSvc := lokstra_registry.MustGetService[*UserService]("user-service")
users := userSvc.GetAll() // Safe to use directly

When to Use:


TryGetService

Retrieves a service with type assertion (safe version).

Signature:

func TryGetService[T any](name string) (T, bool)

Returns:

Example:

if userSvc, ok := lokstra_registry.TryGetService[*UserService]("user-service"); ok {
    users := userSvc.GetAll()
} else {
    log.Println("User service not available")
}

GetServiceAny

Retrieves a service without type assertion (non-generic).

Signature:

func GetServiceAny(name string) (any, bool)

Returns:

Example:

instance, ok := lokstra_registry.GetServiceAny("user-service")
if ok {
    userSvc := instance.(*UserService)
    users := userSvc.GetAll()
}

GetLazyService

Creates a lazy-loading service wrapper.

Signature:

func GetLazyService[T any](serviceName string) *service.Cached[T]

Returns:

Example:

// In handler setup
type UserHandler struct {
    userService *service.Cached[*UserService]
}

func NewUserHandler() *UserHandler {
    return &UserHandler{
        userService: lokstra_registry.GetLazyService[*UserService]("user-service"),
    }
}

// In handler - service loaded only when first accessed
func (h *UserHandler) GetUsers(ctx *request.Context) error {
    users := h.userService.Get().GetAll() // Lazy loaded here!
    return ctx.Api.Ok(users)
}

RegisterService

Registers a pre-instantiated service instance.

Signature:

func RegisterService(name string, instance any)

Example:

userSvc := &UserService{}
lokstra_registry.RegisterService("user-service", userSvc)

Use Cases:


GetServiceFactory

Returns the service factory for a service type.

Signature:

func GetServiceFactory(serviceType string, isLocal bool) deploy.ServiceFactory

Parameters:

Returns:


Middleware Registration

RegisterMiddlewareFactory

Registers a middleware factory function.

Signature:

func RegisterMiddlewareFactory(
    mwType string,
    factory any,
    opts ...RegisterOption,
)

Factory Signatures:

func(config map[string]any) any
func(config map[string]any) request.HandlerFunc // Old pattern

Example:

lokstra_registry.RegisterMiddlewareFactory("logger", func(cfg map[string]any) any {
    level := cfg["level"].(string)
    return func(ctx *request.Context) error {
        log.Printf("[%s] %s %s", level, ctx.R.Method, ctx.R.URL.Path)
        return ctx.Next()
    }
})

// Allow override
lokstra_registry.RegisterMiddlewareFactory("logger", newLoggerFactory,
    lokstra_registry.AllowOverride(true))

RegisterMiddlewareName

Registers a named middleware instance with config.

Signature:

func RegisterMiddlewareName(
    mwName string,
    mwType string,
    config map[string]any,
    opts ...RegisterOption,
)

Example:

// Register factory
lokstra_registry.RegisterMiddlewareFactory("logger", loggerFactory)

// Register named instances with different configs
lokstra_registry.RegisterMiddlewareName("logger-debug", "logger",
    map[string]any{"level": "debug"})
lokstra_registry.RegisterMiddlewareName("logger-info", "logger",
    map[string]any{"level": "info"})

RegisterMiddleware

Registers a pre-instantiated middleware.

Signature:

func RegisterMiddleware(name string, handler request.HandlerFunc)

Example:

logger := func(ctx *request.Context) error {
    log.Printf("%s %s", ctx.R.Method, ctx.R.URL.Path)
    return ctx.Next()
}
lokstra_registry.RegisterMiddleware("logger", logger)

GetMiddleware

Retrieves a middleware instance.

Signature:

func GetMiddleware(name string) (request.HandlerFunc, bool)

Returns:


CreateMiddleware

Creates a middleware from its definition and caches it.

Signature:

func CreateMiddleware(name string) request.HandlerFunc

Example:

logger := lokstra_registry.CreateMiddleware("logger-debug")
router.Use(logger)

Router Registration

RegisterRouter

Registers a router instance.

Signature:

func RegisterRouter(name string, r router.Router)

Example:

userRouter := lokstra.NewRouter()
userRouter.GET("/users", handlers.GetUsers)
lokstra_registry.RegisterRouter("user-router", userRouter)

GetRouter

Retrieves a router instance.

Signature:

func GetRouter(name string) router.Router

Returns:


GetAllRouters

Returns all registered routers.

Signature:

func GetAllRouters() map[string]router.Router

Returns:


Configuration

DefineConfig

Defines a configuration value.

Signature:

func DefineConfig(name string, value any)

Example:

lokstra_registry.DefineConfig("db.dsn", "postgresql://localhost/mydb")
lokstra_registry.DefineConfig("app.max_connections", 100)

GetResolvedConfig

Gets a resolved configuration value.

Signature:

func GetResolvedConfig(key string) (any, bool)

Returns:


GetConfig

Retrieves a configuration value with type assertion and default value.

Signature:

func GetConfig[T any](name string, defaultValue T) T

Parameters:

Returns:

Type Conversion: The function attempts to convert the stored value to type T:

Example:

// Simple types
dsn := lokstra_registry.GetConfig("db.dsn", "postgresql://localhost/default")
maxConn := lokstra_registry.GetConfig("app.max_connections", 10)
debug := lokstra_registry.GetConfig("app.debug", false)

// Complex types
type Features struct {
    EnableLogging bool   `json:"enable_logging"`
    EnableMetrics bool   `json:"enable_metrics"`
    MaxRetries    int    `json:"max_retries"`
}

features := lokstra_registry.GetConfig("app.features", Features{
    EnableLogging: true,
    EnableMetrics: false,
    MaxRetries:    3,
})

YAML Configuration:

configs:
  - name: db.dsn
    value: "${DATABASE_URL:postgresql://localhost/mydb}"
  
  - name: app.max_connections
    value: 25
  
  - name: app.debug
    value: true
  
  - name: app.features
    value:
      enable_logging: true
      enable_metrics: true
      max_retries: 5

Accessing in Service Factory:

func UserServiceFactory(deps map[string]any, config map[string]any) any {
    // Get from service-specific config
    maxItems := 100
    if val, ok := config["max_items"].(int); ok {
        maxItems = val
    }
    
    // Or get from global config registry
    timeout := lokstra_registry.GetConfig("app.timeout", 30)
    
    return &UserServiceImpl{
        MaxItems: maxItems,
        Timeout:  time.Duration(timeout) * time.Second,
    }
}

Best Practices:

Related:


Shutdown Management

ShutdownServices

Gracefully shuts down all services implementing Shutdownable.

Signature:

func ShutdownServices()

Shutdownable Interface:

type Shutdownable interface {
    Shutdown() error
}

Example Service:

type DatabaseService struct {
    conn *sql.DB
}

func (s *DatabaseService) Shutdown() error {
    log.Println("Closing database connection")
    return s.conn.Close()
}

Usage in main.go:

func main() {
    // Register services
    lokstra_registry.RegisterService("db", dbService)
    
    // Setup graceful shutdown
    defer lokstra_registry.ShutdownServices()
    
    // Or with signal handling
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        log.Println("Shutting down...")
        lokstra_registry.ShutdownServices()
        os.Exit(0)
    }()
    
    // Start server
    if err := server.Run(30 * time.Second); err != nil {
        fmt.Println("Error starting server:", err)
    }
}

Advanced Functions

Global

Returns the underlying global registry instance.

Signature:

func Global() *deploy.GlobalRegistry

Returns:

Example:

registry := lokstra_registry.Global()
// Access low-level registry APIs

Complete Examples

Service Registration Pattern

package main

import (
    "github.com/primadi/lokstra"
    "github.com/primadi/lokstra/core/deploy"
    "github.com/primadi/lokstra/lokstra_registry"
)

func main() {
    // Register service types
    lokstra_registry.RegisterServiceType("user-service",
        func(deps, cfg map[string]any) any {
            db := deps["db"].(*service.Cached[*DBService])
            return &UserService{db: db}
        },
        nil,
        deploy.WithResource("user", "users"),
        deploy.WithDependencies("db"),
    )
    
    lokstra_registry.RegisterServiceType("db-service",
        func(cfg map[string]any) any {
            dsn := cfg["dsn"].(string)
            return db.Connect(dsn)
        },
        nil,
    )
    
    // Define service instances
    lokstra_registry.DefineService(&schema.ServiceDef{
        Name:      "db",
        Type:      "db-service",
        Config:    map[string]any{"dsn": "postgresql://localhost/mydb"},
    })
    
    lokstra_registry.DefineService(&schema.ServiceDef{
        Name:      "user-svc",
        Type:      "user-service",
        DependsOn: []string{"db"},
    })
    
    // Access services
    userSvc := lokstra_registry.MustGetService[*UserService]("user-svc")
    users := userSvc.GetAll()
}

Middleware Registration Pattern

func main() {
    // Register middleware factory
    lokstra_registry.RegisterMiddlewareFactory("logger", func(cfg map[string]any) any {
        level := cfg["level"].(string)
        return func(ctx *request.Context) error {
            log.Printf("[%s] %s %s", level, ctx.R.Method, ctx.R.URL.Path)
            return ctx.Next()
        }
    })
    
    // Register named instances
    lokstra_registry.RegisterMiddlewareName("logger-debug", "logger",
        map[string]any{"level": "DEBUG"})
    lokstra_registry.RegisterMiddlewareName("logger-info", "logger",
        map[string]any{"level": "INFO"})
    
    // Use in router
    router := lokstra.NewRouter()
    router.Use(lokstra_registry.CreateMiddleware("logger-info"))
    router.GET("/users", handlers.GetUsers)
}

Lazy Service Pattern

func main() {
    // Register lazy services (no order required!)
    lokstra_registry.RegisterLazyService("db", func(cfg map[string]any) any {
        return db.Connect(cfg["dsn"].(string))
    }, map[string]any{"dsn": "postgresql://localhost/mydb"})
    
    lokstra_registry.RegisterLazyService("user-repo", func() any {
        db := lokstra_registry.MustGetService[*DB]("db")
        return repository.NewUserRepository(db)
    }, nil)
    
    lokstra_registry.RegisterLazyService("user-service", func() any {
        repo := lokstra_registry.MustGetService[*UserRepo]("user-repo")
        return service.NewUserService(repo)
    }, nil)
    
    // Services created on first access
    userSvc := lokstra_registry.MustGetService[*UserService]("user-service")
}

See Also