Service Registration
Detailed guide to service registration patterns and factory functions
Overview
Lokstra provides multiple ways to register services with flexible factory signatures, automatic dependency injection, and lazy loading support. This guide covers all service registration patterns in detail.
Import Path
import (
"github.com/primadi/lokstra/core/deploy"
"github.com/primadi/lokstra/core/deploy/schema"
"github.com/primadi/lokstra/lokstra_registry"
)
Service Type Registration
RegisterServiceType
The primary method for registering reusable service factories.
Signature:
func RegisterServiceType(
serviceType string,
local, remote any,
options ...deploy.RegisterServiceTypeOption,
)
Parameters:
serviceType- Unique identifier for this service typelocal- Local factory (in-process implementation)remote- Remote factory (API client wrapper), ornilif not neededoptions- Optional metadata for auto-router, dependencies, etc.
Factory Signatures
Lokstra auto-wraps three factory signatures to the canonical form:
1. No Parameters (Simplest)
func() any
Use When:
- Service has no configuration
- Service has no dependencies
- Simple stateless services
Example:
lokstra_registry.RegisterServiceType("health-checker",
func() any {
return &HealthChecker{}
},
nil,
)
2. Config Only
func(cfg map[string]any) any
Use When:
- Service needs configuration
- No dependencies on other services
- Configuration-driven initialization
Example:
lokstra_registry.RegisterServiceType("db-service",
func(cfg map[string]any) any {
dsn := cfg["dsn"].(string)
maxConn := cfg["max_connections"].(int)
return db.NewConnection(dsn, maxConn)
},
nil,
)
Config in YAML:
service-definitions:
db:
type: db-service
config:
dsn: "postgresql://localhost/mydb"
max_connections: 10
3. Dependencies + Config (Full Control)
func(deps, cfg map[string]any) any
Use When:
- Service depends on other services
- Need both dependencies and configuration
- Complex initialization logic
Example:
lokstra_registry.RegisterServiceType("order-service",
func(deps, cfg map[string]any) any {
// Extract dependencies (lazy-loaded wrappers)
userSvc := deps["userService"].(*service.Cached[*UserService])
paymentSvc := deps["paymentService"].(*service.Cached[*PaymentService])
// Extract config
maxOrders := cfg["max_orders"].(int)
return &OrderService{
userService: userSvc,
paymentService: paymentSvc,
maxOrders: maxOrders,
}
},
nil,
deploy.WithDependencies("userService", "paymentService"),
)
Dependency Resolution:
- Dependencies are provided as
*service.Cached[T](lazy wrappers) - Call
.Get()to resolve the actual service instance - Thread-safe, single initialization
- Prevents circular dependency issues
Registration Options
WithResource
Specifies resource names for auto-router generation.
Signature:
deploy.WithResource(singular, plural string)
Example:
lokstra_registry.RegisterServiceType("user-service",
userFactory,
nil,
deploy.WithResource("user", "users"),
)
Generated Routes:
GET /users -> List
POST /users -> Create
GET /users/:id -> Get
PUT /users/:id -> Update
DELETE /users/:id -> Delete
WithConvention
Specifies routing convention (default: βrestβ).
Signature:
deploy.WithConvention(convention string)
Supported Conventions:
"rest"- RESTful routing (default)"rpc"- RPC-style routing- Custom conventions (register with
convention.Register)
Example:
lokstra_registry.RegisterServiceType("user-service",
userFactory,
nil,
deploy.WithResource("user", "users"),
deploy.WithConvention("rest"),
)
WithDependencies
Declares service dependencies for automatic injection.
Signature:
deploy.WithDependencies(deps ...string)
Example:
lokstra_registry.RegisterServiceType("order-service",
orderFactory,
nil,
deploy.WithDependencies("userService", "paymentService", "db"),
)
Dependency Mapping:
service-definitions:
order-svc:
type: order-service
depends-on:
- user-svc
- payment-svc
- db-connection
Framework automatically maps:
"userService"βuser-svcinstance"paymentService"βpayment-svcinstance"db"βdb-connectioninstance
WithPathPrefix
Sets path prefix for all routes.
Signature:
deploy.WithPathPrefix(prefix string)
Example:
lokstra_registry.RegisterServiceType("user-service",
userFactory,
nil,
deploy.WithResource("user", "users"),
deploy.WithPathPrefix("/api/v1"),
)
Generated Routes:
GET /api/v1/users
POST /api/v1/users
GET /api/v1/users/:id
...
WithMiddleware
Attaches middleware to all service routes.
Signature:
deploy.WithMiddleware(names ...string)
Example:
lokstra_registry.RegisterServiceType("user-service",
userFactory,
nil,
deploy.WithResource("user", "users"),
deploy.WithMiddleware("auth", "logger", "rate-limiter"),
)
WithRouteOverride
Customizes path for specific methods.
Signature:
deploy.WithRouteOverride(methodName, path string)
Example:
lokstra_registry.RegisterServiceType("user-service",
userFactory,
nil,
deploy.WithResource("user", "users"),
deploy.WithRouteOverride("Login", "/auth/login"),
deploy.WithRouteOverride("Logout", "/auth/logout"),
)
Result:
POST /auth/login -> user-service.Login()
POST /auth/logout -> user-service.Logout()
GET /users -> user-service.List()
WithHiddenMethods
Excludes methods from auto-router generation.
Signature:
deploy.WithHiddenMethods(methods ...string)
Example:
lokstra_registry.RegisterServiceType("user-service",
userFactory,
nil,
deploy.WithResource("user", "users"),
deploy.WithHiddenMethods("Delete", "InternalHelper"),
)
Local vs Remote Factories
Local Factory
In-process service implementation.
Example:
func UserServiceLocalFactory(deps, cfg map[string]any) any {
db := deps["db"].(*service.Cached[*DBService])
return &UserService{
db: db,
}
}
Remote Factory
API client wrapper for external services.
Example:
func UserServiceRemoteFactory(deps, cfg map[string]any) any {
proxyService := deps["remote"].(*proxy.Service)
return &UserServiceRemote{
proxy: proxyService,
}
}
Registration:
lokstra_registry.RegisterServiceType("user-service",
UserServiceLocalFactory, // Local implementation
UserServiceRemoteFactory, // Remote implementation
deploy.WithResource("user", "users"),
deploy.WithDependencies("db"),
)
Framework automatically:
- Uses local factory for local services
- Uses remote factory when service URL is provided
- Injects
remotedependency with*proxy.Service
Service Definition
DefineService (Code)
Code-based service instance definition.
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": 100,
},
DependsOn: []string{"db-service"},
})
DefineService (YAML)
YAML-based service definition.
Example:
service-definitions:
user-service:
type: user-service-factory
config:
max_connections: 100
depends-on:
- db-service
Lazy Service Registration
RegisterLazyService
Simple lazy service registration without explicit dependency declaration.
Signature:
func RegisterLazyService(name string, factory any, config map[string]any)
Factory Signatures:
func() any // No params
func(cfg map[string]any) any // With config
Benefits:
- β Register services in any order
- β Dependencies resolved on first access
- β Thread-safe singleton
- β Services only created when needed
Example:
// No order required!
lokstra_registry.RegisterLazyService("user-service", func() any {
repo := lokstra_registry.MustGetService[*UserRepo]("user-repo")
return &UserService{repo: repo}
}, nil)
lokstra_registry.RegisterLazyService("user-repo", func() any {
db := lokstra_registry.MustGetService[*DB]("db")
return &UserRepository{db: db}
}, nil)
lokstra_registry.RegisterLazyService("db", func(cfg map[string]any) any {
return db.Connect(cfg["dsn"].(string))
}, map[string]any{"dsn": "postgresql://localhost/mydb"})
RegisterLazyServiceWithDeps
Lazy service with explicit dependency injection.
Signature:
func RegisterLazyServiceWithDeps(
name string,
factory any,
deps map[string]string,
config map[string]any,
opts ...deploy.LazyServiceOption,
)
Factory Signature:
func(deps, cfg map[string]any) any
Dependency Mapping:
deps := map[string]string{
"userRepo": "user-repo", // key in factory -> service name
"paymentSvc": "payment-service",
}
Benefits:
- β Explicit dependency declaration
- β Framework auto-injects dependencies
- β
No manual
MustGetService()calls - β Clear dependency graph
Example:
lokstra_registry.RegisterLazyServiceWithDeps("order-service",
func(deps, cfg map[string]any) any {
// deps already contains resolved services!
userRepo := deps["userRepo"].(*UserRepository)
paymentSvc := deps["paymentSvc"].(*PaymentService)
maxOrders := cfg["max_orders"].(int)
return &OrderService{
userRepo: userRepo,
paymentSvc: paymentSvc,
maxOrders: maxOrders,
}
},
map[string]string{
"userRepo": "user-repo",
"paymentSvc": "payment-service",
},
map[string]any{"max_orders": 100},
)
Registration Modes:
// Panic if already registered (default)
lokstra_registry.RegisterLazyServiceWithDeps(name, factory, deps, cfg)
// Skip if already registered (idempotent)
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))
Complete Examples
Simple Service (No Dependencies)
package main
import "github.com/primadi/lokstra/lokstra_registry"
type HealthChecker struct{}
func (h *HealthChecker) Check() string {
return "OK"
}
func main() {
// Register type
lokstra_registry.RegisterServiceType("health-checker",
func() any {
return &HealthChecker{}
},
nil,
)
// Define instance
lokstra_registry.DefineService(&schema.ServiceDef{
Name: "health",
Type: "health-checker",
})
// Access
health := lokstra_registry.MustGetService[*HealthChecker]("health")
status := health.Check()
}
Service with Config
type DBService struct {
dsn string
maxConn int
}
func main() {
lokstra_registry.RegisterServiceType("db-service",
func(cfg map[string]any) any {
return &DBService{
dsn: cfg["dsn"].(string),
maxConn: cfg["max_connections"].(int),
}
},
nil,
)
lokstra_registry.DefineService(&schema.ServiceDef{
Name: "db",
Type: "db-service",
Config: map[string]any{
"dsn": "postgresql://localhost/mydb",
"max_connections": 10,
},
})
}
Service with Dependencies
type UserService struct {
db *service.Cached[*DBService]
logger *service.Cached[*Logger]
}
func (s *UserService) GetUser(id int) (*User, error) {
db := s.db.Get() // Lazy load
logger := s.logger.Get()
logger.Info("Getting user", id)
return db.QueryUser(id)
}
func main() {
lokstra_registry.RegisterServiceType("user-service",
func(deps, cfg map[string]any) any {
return &UserService{
db: deps["db"].(*service.Cached[*DBService]),
logger: deps["logger"].(*service.Cached[*Logger]),
}
},
nil,
deploy.WithDependencies("db", "logger"),
)
lokstra_registry.DefineService(&schema.ServiceDef{
Name: "user-svc",
Type: "user-service",
DependsOn: []string{"db", "logger-svc"},
})
}
Service with Auto-Router
type UserService struct {
db *service.Cached[*DBService]
}
func (s *UserService) List(ctx *request.Context) error {
users := s.db.Get().QueryAll()
return ctx.Api.Ok(users)
}
func (s *UserService) Get(ctx *request.Context) error {
id := ctx.Req.PathParam("id")
user := s.db.Get().QueryUser(id)
return ctx.Api.Ok(user)
}
func (s *UserService) Create(ctx *request.Context) error {
var user User
ctx.Req.BindJSON(&user)
s.db.Get().Insert(&user)
return ctx.Api.Created(user)
}
func main() {
lokstra_registry.RegisterServiceType("user-service",
userFactory,
nil,
deploy.WithResource("user", "users"),
deploy.WithConvention("rest"),
deploy.WithDependencies("db"),
deploy.WithMiddleware("auth", "logger"),
)
// Auto-generates:
// GET /users -> List
// POST /users -> Create
// GET /users/:id -> Get
// PUT /users/:id -> Update
// DELETE /users/:id -> Delete
}
Remote Service Pattern
// Local implementation
type UserServiceLocal struct {
db *service.Cached[*DBService]
}
func (s *UserServiceLocal) GetUser(id int) (*User, error) {
return s.db.Get().QueryUser(id)
}
// Remote implementation (API client)
type UserServiceRemote struct {
proxy *proxy.Service
}
func (s *UserServiceRemote) GetUser(id int) (*User, error) {
return proxy.CallWithData[*User](s.proxy, "GetUser", id)
}
// Factories
func UserServiceLocalFactory(deps, cfg map[string]any) any {
return &UserServiceLocal{
db: deps["db"].(*service.Cached[*DBService]),
}
}
func UserServiceRemoteFactory(deps, cfg map[string]any) any {
return &UserServiceRemote{
proxy: deps["remote"].(*proxy.Service),
}
}
func main() {
lokstra_registry.RegisterServiceType("user-service",
UserServiceLocalFactory,
UserServiceRemoteFactory,
deploy.WithResource("user", "users"),
deploy.WithDependencies("db"),
)
}
YAML Configuration:
# Local service
servers:
- name: api
services:
- user-service
# Remote service
servers:
- name: web
external-service-definitions:
user-service:
url: http://api.example.com
type: user-service # Uses remote factory
Best Practices
1. Use Appropriate Factory Signature
// β
Good: Minimal signature for simple services
lokstra_registry.RegisterServiceType("health", func() any {
return &HealthChecker{}
}, nil)
// π« Avoid: Unnecessary params
lokstra_registry.RegisterServiceType("health", func(deps, cfg map[string]any) any {
return &HealthChecker{}
}, nil)
2. Declare Dependencies Explicitly
// β
Good: Clear dependencies
lokstra_registry.RegisterServiceType("user-service", factory, nil,
deploy.WithDependencies("db", "logger"))
// π« Avoid: Hidden dependencies
lokstra_registry.RegisterServiceType("user-service", factory, nil)
3. Use Lazy Wrappers for Dependencies
// β
Good: Lazy-loaded dependencies
type UserService struct {
db *service.Cached[*DBService]
}
// π« Avoid: Direct dependencies (breaks lazy loading)
type UserService struct {
db *DBService
}
4. Provide Both Local and Remote When Needed
// β
Good: Supports both local and remote deployment
lokstra_registry.RegisterServiceType("user-service",
UserServiceLocalFactory,
UserServiceRemoteFactory,
deploy.WithResource("user", "users"),
)
// π« Avoid: Only local (can't call remotely)
lokstra_registry.RegisterServiceType("user-service",
UserServiceLocalFactory,
nil,
deploy.WithResource("user", "users"),
)
See Also
- lokstra_registry - Registry API
- Service - Lazy service loading
- Middleware Registration - Middleware patterns
- Deploy - Deployment configuration
Related Guides
- Service Essentials - Service basics
- Dependency Injection - Advanced DI patterns
- Auto-Router - Auto-router generation