Example 01: Simple Service
Learn service registration and basic access patterns
Time: 10 minutes β’ Concepts: Service factory, registration, LazyLoad
π― What Youβll Learn
- β Define a service with business logic
- β Create a service factory function
- β Register service in the registry
- β
Access service with
service.LazyLoad() - β
Use
MustGet()for clear error messages
π Run It
cd docs/01-router-guide/02-service/examples/01-simple-service
go run main.go
Server starts on: http://localhost:3000
π Code Walkthrough
Step 1: Define Service
type UserService struct {
users []User
nextID int
}
func (s *UserService) GetAll() ([]User, error) {
return s.users, nil
}
func (s *UserService) GetByID(id int) (*User, error) {
for _, user := range s.users {
if user.ID == id {
return &user, nil
}
}
return nil, fmt.Errorf("user not found")
}
func (s *UserService) Create(name, email, role string) (*User, error) {
user := User{
ID: s.nextID,
Name: name,
Email: email,
Role: role,
}
s.users = append(s.users, user)
s.nextID++
return &user, nil
}
π Key Points:
- Service contains business logic
- Methods return
(data, error)for proper error handling - In-memory storage for simplicity (use DB in production!)
Step 2: Create Factory Function
func NewUserService() *UserService {
return &UserService{
users: []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", Role: "admin"},
{ID: 2, Name: "Bob", Email: "bob@example.com", Role: "user"},
},
nextID: 3,
}
}
π Key Points:
- Factory initializes service with dependencies
- Can inject DB, cache, other services, etc.
- Called once during app initialization
Step 3: Register Service
func main() {
// Create and register service instance
userSvc := NewUserService()
lokstra_registry.RegisterService("users", userSvc)
// ... create router and handlers
}
π Key Points:
- Register before creating app
- Service available to all routers and handlers
- Can be accessed by name βusersβ
Step 4: Access Service with LazyLoad
// Package-level: Cached after first access!
var userService = service.LazyLoad[*UserService]("users")
r.GET("/users", func() (*response.ApiHelper, error) {
api := response.NewApiHelper()
// Access service - only 1-5ns overhead after first call!
users, err := userService.MustGet().GetAll()
if err != nil {
api.InternalError(err.Error())
return api, nil
}
api.Ok(users)
return api, nil
})
π Key Points:
LazyLoadat package-level (not function-level!)MustGet()panics with clear error if service not found- Cached after first access (20-100x faster than registry lookup!)
π§ͺ Test Endpoints
List All Users
curl http://localhost:3000/users
Response:
{
"status": "success",
"data": [
{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"},
{"id": 2, "name": "Bob", "email": "bob@example.com", "role": "user"},
{"id": 3, "name": "Charlie", "email": "charlie@example.com", "role": "user"}
]
}
Get User by ID
curl http://localhost:3000/users/1
Response:
{
"status": "success",
"data": {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"role": "admin"
}
}
Create New User
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{
"name": "Dave",
"email": "dave@example.com",
"role": "user"
}'
Response:
{
"status": "success",
"message": "User created successfully",
"data": {
"id": 4,
"name": "Dave",
"email": "dave@example.com",
"role": "user"
}
}
Update User
curl -X PUT http://localhost:3000/users/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Alice Updated",
"email": "alice.new@example.com",
"role": "admin"
}'
Delete User
curl -X DELETE http://localhost:3000/users/3
Response:
{
"status": "success",
"data": {
"message": "User deleted successfully"
}
}
π‘ Key Concepts
1. Service Pattern
Services encapsulate business logic:
- β Reusable across handlers
- β Testable independently
- β Clear responsibility separation
2. Factory Pattern
Factory functions initialize services:
- β Dependency injection
- β Error handling during setup
- β Configuration
3. LazyLoad Pattern
service.LazyLoad() provides cached access:
- β Fast: 1-5ns after first access
- β Clear errors: MustGet() panics with service name
- β Simple: No manual caching needed
4. Registry Pattern
Global service registry:
- β Centralized service management
- β Name-based access
- β Dependency resolution
π― Best Practices Demonstrated
β Package-Level LazyLoad
// β
GOOD: Package-level, cached forever
var userService = service.LazyLoad[*UserService]("users")
func handler() {
users := userService.MustGet().GetAll()
}
β Function-Level LazyLoad
// β BAD: Created every request, cache useless!
func handler() {
userService := service.LazyLoad[*UserService]("users")
users := userService.MustGet().GetAll()
}
β Use MustGet() for Clear Errors
// β
GOOD: Clear error message
users := userService.MustGet().GetAll()
// If service not found: "service 'users' not found or not initialized"
β Using Get() Without Nil Check
// β BAD: Confusing nil pointer error
users := userService.Get().GetAll()
// If service not found: "runtime error: invalid memory address"
π Whatβs Next?
Continue to:
- Example 02 - LazyLoad vs GetService - Performance comparison
- Example 03 - Service Dependencies - DI pattern
- Example 04 - Service as Router - Auto-generate endpoints
Related:
Back: Service Guide
Next: 02 - Performance Comparison