Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

Plugin System Overview

The LastBackend Toolkit plugin system provides seamless integration with external services through a unified interface. Plugins handle initialization, configuration, lifecycle management, and provide type-safe interfaces for your application code.

How Plugins Work

1. Declaration in Proto Files

Plugins are declared using toolkit annotations:

// Database plugin
option (toolkit.plugins) = {
  prefix: "pgsql"           // Environment variable prefix
  plugin: "postgres_gorm"   // Plugin type from toolkit-plugins
};

// Cache plugin
option (toolkit.plugins) = {
  prefix: "cache"
  plugin: "redis"
};

2. Automatic Code Generation

The toolkit generates:

  • Plugin interfaces for dependency injection
  • Plugin initialization code
  • Environment variable parsing
  • Lifecycle management hooks

3. Environment Configuration

Plugins automatically parse environment variables:

# PostgreSQL plugin (prefix: "pgsql")
MYSERVICE_PGSQL_HOST=localhost
MYSERVICE_PGSQL_PORT=5432
MYSERVICE_PGSQL_USERNAME=user
MYSERVICE_PGSQL_PASSWORD=secret

# Redis plugin (prefix: "cache")
MYSERVICE_CACHE_HOST=localhost
MYSERVICE_CACHE_PORT=6379

4. Dependency Injection

Plugins are automatically injected into your components:

func NewRepository(app toolkit.Service, db servicepb.PgsqlPlugin) *Repository {
    return &Repository{
        db:  db.DB(), // *gorm.DB ready to use
        log: app.Log(),
    }
}

Available Plugins

Plugin Package Purpose Interface
postgres_gorm postgres_gorm PostgreSQL with GORM ORM Plugin.DB() *gorm.DB
postgres_pg postgres_pg PostgreSQL with go-pg Plugin.DB() *pg.DB
postgres_pgx postgres_pgx PostgreSQL with pgx driver Plugin.DB() *pgxpool.Pool
redis redis Redis cache/pub-sub Plugin.Client() redis.Cmdable
rabbitmq rabbitmq Message queue Plugin.Publish/Subscribe
centrifuge centrifuge Real-time messaging Plugin.Node() *centrifuge.Node
sentry sentry Error monitoring Error tracking integration
resolver_consul resolver_consul Service discovery Consul integration

Plugin Architecture

Lifecycle Management

Plugins follow a structured lifecycle:

  1. Initialization - Plugin instances created with configuration
  2. PreStart - Connections established, resources initialized
  3. OnStart - Background processes started (async)
  4. Running - Plugin available for use
  5. OnStop - Graceful shutdown, resources cleaned up

Configuration Hierarchy

Environment variables follow this pattern:

{SERVICE_PREFIX}_{PLUGIN_PREFIX}_{SETTING_NAME}

Example:

  • Service prefix: USER_SERVICE (from runtime.WithEnvPrefix)
  • Plugin prefix: PGSQL (from proto declaration)
  • Setting: HOST
  • Result: USER_SERVICE_PGSQL_HOST=localhost

Multi-Instance Support

You can use multiple instances of the same plugin type:

// Primary database
option (toolkit.plugins) = {
  prefix: "primary_db"
  plugin: "postgres_gorm"
};

// Analytics database
option (toolkit.plugins) = {
  prefix: "analytics_db"
  plugin: "postgres_gorm"
};

// Session cache
option (toolkit.plugins) = {
  prefix: "session_cache"
  plugin: "redis"
};

// Data cache
option (toolkit.plugins) = {
  prefix: "data_cache"
  plugin: "redis"
};

Quick Start

1. Choose Plugins

Determine which plugins your service needs:

  • Database: Choose between GORM, go-pg, or pgx
  • Cache: Redis for most use cases
  • Message Queue: RabbitMQ for reliable messaging
  • Real-time: Centrifuge for WebSocket connections
  • Monitoring: Sentry for error tracking

2. Declare in Proto

option (toolkit.plugins) = {
  prefix: "pgsql"
  plugin: "postgres_gorm"
};

option (toolkit.plugins) = {
  prefix: "cache"
  plugin: "redis"
};

3. Set Environment Variables

MYSERVICE_PGSQL_HOST=localhost
MYSERVICE_PGSQL_USERNAME=user
MYSERVICE_PGSQL_PASSWORD=secret

MYSERVICE_CACHE_HOST=localhost
MYSERVICE_CACHE_PORT=6379

4. Use in Code

func NewRepository(app toolkit.Service, db servicepb.PgsqlPlugin) *Repository {
    return &Repository{db: db.DB()}
}

func (r *Repository) CreateUser(ctx context.Context, user *User) error {
    return r.db.WithContext(ctx).Create(user).Error
}

Best Practices

1. Use Descriptive Prefixes

// ✅ Good - clear purpose
option (toolkit.plugins) = { prefix: "user_db", plugin: "postgres_gorm" };
option (toolkit.plugins) = { prefix: "session_cache", plugin: "redis" };

// ❌ Bad - unclear purpose
option (toolkit.plugins) = { prefix: "db1", plugin: "postgres_gorm" };
option (toolkit.plugins) = { prefix: "cache1", plugin: "redis" };

2. Handle Plugin Errors Gracefully

func (s *Service) GetUser(ctx context.Context, id string) (*User, error) {
    // Try cache first (non-critical)
    if cached := s.getCachedUser(ctx, id); cached != nil {
        return cached, nil
    }
    
    // Fallback to database (critical)
    user, err := s.repo.GetUser(ctx, id)
    if err != nil {
        return nil, err
    }
    
    // Update cache (best effort)
    s.cacheUser(ctx, user)
    
    return user, nil
}

3. Separate Critical from Non-Critical

  • Critical plugins (database): Fail service startup if unavailable
  • Non-critical plugins (cache): Degrade gracefully if unavailable

4. Use Plugin Health Checks

func (s *Server) HealthCheck(ctx context.Context, req *HealthCheckRequest) (*HealthCheckResponse, error) {
    status := "healthy"
    
    // Check critical plugins
    if err := s.db.DB().Raw("SELECT 1").Error; err != nil {
        return nil, status.Error(codes.Unavailable, "database unhealthy")
    }
    
    // Check non-critical plugins (don't fail)
    if err := s.cache.Client().Ping(ctx).Err(); err != nil {
        s.app.Log().Warn("cache unavailable")
    }
    
    return &HealthCheckResponse{Status: status}, nil
}

Documentation

Examples

See the examples directory for real-world plugin usage:

  • service/ - PostgreSQL + Redis + RabbitMQ
  • gateway/ - Plugin-free API gateway
  • wss/ - Redis + Centrifuge for real-time features

Getting Help

The plugin system eliminates infrastructure boilerplate and lets you focus on business logic while maintaining type safety and clean architecture.