database

package module
v1.0.19 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2025 License: MIT Imports: 12 Imported by: 0

README

Database Module for Modular

Go Reference Modules CI

A Modular module that provides database connectivity and management.

Overview

The Database module provides a service for connecting to and interacting with SQL databases. It wraps the standard Go database/sql package to provide a clean, service-oriented interface that integrates with the Modular framework.

Features

  • Support for multiple database connections with named configurations
  • Connection pooling with configurable settings
  • Simplified interface for common database operations
  • Context-aware database operations for proper cancellation and timeout handling
  • Support for transactions

Installation

go get github.com/GoCodeAlone/modular/modules/database

Usage

Importing Database Drivers

The database module uses the standard Go database/sql package, which requires you to import the specific database driver you plan to use as a side-effect. Make sure to import your desired driver in your main package:

import (
    "github.com/GoCodeAlone/modular"
    "github.com/GoCodeAlone/modular/modules/database"
    
    // Import database drivers as needed
    _ "github.com/lib/pq"           // PostgreSQL driver
    _ "github.com/go-sql-driver/mysql"    // MySQL driver
    _ "github.com/mattn/go-sqlite3"  // SQLite driver
)

You'll also need to add the driver to your module's dependencies:

# For PostgreSQL
go get github.com/lib/pq

# For MySQL
go get github.com/go-sql-driver/mysql

# For SQLite
go get github.com/mattn/go-sqlite3
Registering the Module
import (
    "github.com/GoCodeAlone/modular"
    "github.com/GoCodeAlone/modular/modules/database"
    _ "github.com/lib/pq" // Import PostgreSQL driver
)

func main() {
    app := modular.NewStdApplication(
        modular.NewStdConfigProvider(configMap),
        logger,
    )
    
    // Register the database module
    app.RegisterModule(database.NewModule())
    
    // Register your modules that depend on the database service
    app.RegisterModule(NewYourModule())
    
    // Run the application
    if err := app.Run(); err != nil {
        logger.Error("Application error", "error", err)
    }
}
Configuration

Configure the database module in your application configuration:

database:
  default: "postgres_main"
  connections:
    postgres_main:
      driver: "postgres"
      dsn: "postgres://user:password@localhost:5432/dbname?sslmode=disable"
      max_open_connections: 25
      max_idle_connections: 5
      connection_max_lifetime: 300  # seconds
      connection_max_idle_time: 60  # seconds
    mysql_reporting:
      driver: "mysql"
      dsn: "user:password@tcp(localhost:3306)/reporting"
      max_open_connections: 10
      max_idle_connections: 2
      connection_max_lifetime: 600  # seconds
AWS IAM Authentication

The database module supports AWS IAM authentication for RDS databases. When enabled, the module will automatically obtain and refresh IAM authentication tokens from AWS, using them as database passwords.

Configuration with AWS IAM Auth
database:
  default: "postgres_rds"
  connections:
    postgres_rds:
      driver: "postgres"
      # DSN without password - IAM token will be used as password
      dsn: "postgres://iamuser@mydb.cluster-xyz.us-east-1.rds.amazonaws.com:5432/mydb?sslmode=require"
      max_open_connections: 25
      max_idle_connections: 5
      connection_max_lifetime: 300  # seconds
      connection_max_idle_time: 60  # seconds
      aws_iam_auth:
        enabled: true
        region: "us-east-1"                    # AWS region where RDS instance is located
        db_user: "iamuser"                     # Database username for IAM authentication
        token_refresh_interval: 600            # Token refresh interval in seconds (default: 600)
AWS IAM Auth Configuration Options
  • enabled: Boolean flag to enable AWS IAM authentication
  • region: AWS region where the RDS instance is located (required)
  • db_user: Database username configured for IAM authentication (required)
  • token_refresh_interval: How often to refresh the IAM token in seconds (default: 600 seconds / 10 minutes)
Prerequisites for AWS IAM Authentication
  1. RDS Instance Configuration: Your RDS instance must have IAM database authentication enabled.

  2. IAM Policy: The application must have IAM permissions to connect to RDS:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "rds-db:connect"
                ],
                "Resource": [
                    "arn:aws:rds-db:us-east-1:123456789012:dbuser:db-instance-id/iamuser"
                ]
            }
        ]
    }
    
  3. Database User: Create a database user and grant the rds_iam role:

    -- For PostgreSQL
    CREATE USER iamuser;
    GRANT rds_iam TO iamuser;
    GRANT CONNECT ON DATABASE mydb TO iamuser;
    GRANT USAGE ON SCHEMA public TO iamuser;
    GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO iamuser;
    
    -- For MySQL
    CREATE USER iamuser IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
    GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO iamuser;
    
  4. AWS Credentials: The application must have AWS credentials available through one of:

    • Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
    • IAM instance profile (for EC2 instances)
    • AWS credentials file
    • IAM roles for service accounts (for EKS)
How It Works
  1. When the database module starts, it checks if AWS IAM authentication is enabled for each connection.
  2. If enabled, it creates an AWS IAM token provider using the specified region and database user.
  3. The token provider generates an initial IAM authentication token using the AWS SDK.
  4. The original DSN is modified to use this token as the password.
  5. A background goroutine refreshes the token at the specified interval (default: 10 minutes).
  6. Tokens are automatically refreshed before they expire (RDS IAM tokens are valid for 15 minutes).
Dependencies

AWS IAM authentication requires these additional dependencies:

go get github.com/aws/aws-sdk-go-v2/aws
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/feature/rds/auth

These are automatically added when you use the database module with AWS IAM authentication enabled.

Using the Database Service
type YourModule struct {
    dbService database.DatabaseService
}

// Request the database service
func (m *YourModule) RequiresServices() []modular.ServiceDependency {
    return []modular.ServiceDependency{
        {
            Name:     "database.service",
            Required: true,
        },
        // If you need a specific database connection:
        {
            Name:     "database.service.mysql_reporting",
            Required: true,
        },
    }
}

// Inject the service using constructor injection
func (m *YourModule) Constructor() modular.ModuleConstructor {
    return func(app *modular.StdApplication, services map[string]any) (modular.Module, error) {
        // Get the default database service
        dbService, ok := services["database.service"].(database.DatabaseService)
        if !ok {
            return nil, fmt.Errorf("service 'database.service' not found or wrong type")
        }
        
        // Get a specific database connection
        reportingDB, ok := services["database.service.mysql_reporting"].(database.DatabaseService)
        if !ok {
            return nil, fmt.Errorf("service 'database.service.mysql_reporting' not found or wrong type")
        }
        
        return &YourModule{
            dbService: dbService,
        }, nil
    }
}

// Example of using the database service
func (m *YourModule) GetUserData(ctx context.Context, userID int64) (*User, error) {
    user := &User{}
    
    row := m.dbService.QueryRowContext(ctx, 
        "SELECT id, name, email FROM users WHERE id = $1", userID)
    
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, fmt.Errorf("user not found: %d", userID)
        }
        return nil, err
    }
    
    return user, nil
}

// Example of a transaction
func (m *YourModule) TransferFunds(ctx context.Context, fromAccount, toAccount int64, amount float64) error {
    tx, err := m.dbService.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    
    defer func() {
        if err != nil {
            tx.Rollback()
            return
        }
    }()
    
    // Debit from source account
    _, err = tx.ExecContext(ctx, 
        "UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromAccount)
    if err != nil {
        return err
    }
    
    // Credit to destination account
    _, err = tx.ExecContext(ctx,
        "UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, toAccount)
    if err != nil {
        return err
    }
    
    // Commit the transaction
    return tx.Commit()
}
Working with multiple database connections
type MultiDBModule struct {
    dbManager *database.Module
}

// Request the database manager
func (m *MultiDBModule) RequiresServices() []modular.ServiceDependency {
    return []modular.ServiceDependency{
        {
            Name:     "database.manager",
            Required: true,
        },
    }
}

// Inject the manager using constructor injection
func (m *MultiDBModule) Constructor() modular.ModuleConstructor {
    return func(app *modular.StdApplication, services map[string]any) (modular.Module, error) {
        dbManager, ok := services["database.manager"].(*database.Module)
        if !ok {
            return nil, fmt.Errorf("service 'database.manager' not found or wrong type")
        }
        
        return &MultiDBModule{
            dbManager: dbManager,
        }, nil
    }
}

// Example of using multiple database connections
func (m *MultiDBModule) ProcessData(ctx context.Context) error {
    // Get specific connections
    sourceDB, exists := m.dbManager.GetConnection("postgres_main")
    if !exists {
        return fmt.Errorf("source database connection not found")
    }
    
    reportingDB, exists := m.dbManager.GetConnection("mysql_reporting")
    if !exists {
        return fmt.Errorf("reporting database connection not found")
    }
    
    // Read from source
    rows, err := sourceDB.QueryContext(ctx, "SELECT id, data FROM source_table")
    if err != nil {
        return err
    }
    defer rows.Close()
    
    // Process and write to reporting
    for rows.Next() {
        var id int64
        var data string
        
        if err := rows.Scan(&id, &data); err != nil {
            return err
        }
        
        // Process and write to reporting DB
        _, err = reportingDB.ExecuteContext(ctx,
            "INSERT INTO processed_data (source_id, data) VALUES (?, ?)", 
            id, processData(data))
        if err != nil {
            return err
        }
    }
    
    return rows.Err()
}

API Reference

Types
DatabaseService
type DatabaseService interface {
    // Connect establishes the database connection
    Connect() error
    
    // Close closes the database connection
    Close() error
    
    // DB returns the underlying database connection
    DB() *sql.DB
    
    // Ping verifies the database connection is still alive
    Ping(ctx context.Context) error
    
    // Stats returns database statistics
    Stats() sql.DBStats
    
    // ExecuteContext executes a query without returning any rows
    ExecuteContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
    
    // Execute executes a query without returning any rows (using default context)
    Execute(query string, args ...interface{}) (sql.Result, error)
    
    // QueryContext executes a query that returns rows
    QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
    
    // Query executes a query that returns rows (using default context)
    Query(query string, args ...interface{}) (*sql.Rows, error)
    
    // QueryRowContext executes a query that returns a single row
    QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
    
    // QueryRow executes a query that returns a single row (using default context)
    QueryRow(query string, args ...interface{}) *sql.Row
    
    // BeginTx starts a transaction
    BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
    
    // Begin starts a transaction with default options
    Begin() (*sql.Tx, error)
}
Module Manager Methods
// GetConnection returns a database service by name
func (m *Module) GetConnection(name string) (DatabaseService, bool)

// GetDefaultConnection returns the default database service
func (m *Module) GetDefaultConnection() DatabaseService

// GetConnections returns all configured database connections
func (m *Module) GetConnections() map[string]DatabaseService

License

MIT License

Documentation

Overview

Package database provides database connectivity and management for modular applications. This module supports multiple database connections with different drivers and provides a unified interface for database operations.

The database module features:

  • Multiple database connections with configurable drivers (MySQL, PostgreSQL, SQLite, etc.)
  • Connection pooling and health monitoring
  • Default connection selection for simplified usage
  • Database service abstraction for testing and mocking
  • Instance-aware configuration for environment-specific settings

Usage:

app.RegisterModule(database.NewModule())

The module registers database services that provide access to sql.DB instances and higher-level database operations. Other modules can depend on these services for database access.

Configuration:

The module requires a "database" configuration section with connection details
for each database instance, including driver, DSN, and connection pool settings.

Index

Constants

View Source
const Name = "database"

Module name constant for service registration and dependency resolution.

Variables

View Source
var (
	ErrIAMAuthNotEnabled     = errors.New("AWS IAM auth not enabled")
	ErrIAMRegionRequired     = errors.New("AWS region is required for IAM authentication")
	ErrIAMDBUserRequired     = errors.New("database user is required for IAM authentication")
	ErrExtractEndpointFailed = errors.New("could not extract endpoint from DSN")
	ErrNoUserInfoInDSN       = errors.New("no user information in DSN to replace password")
)
View Source
var (
	ErrInvalidConfigType = errors.New("invalid config type for database module")
	ErrMissingDriver     = errors.New("database connection missing driver")
	ErrMissingDSN        = errors.New("database connection missing DSN")
)
View Source
var (
	ErrEmptyDriver          = errors.New("database driver cannot be empty")
	ErrEmptyDSN             = errors.New("database connection string (DSN) cannot be empty")
	ErrDatabaseNotConnected = errors.New("database not connected")
)

Define static errors

View Source
var (
	ErrNoDefaultService = errors.New("no default database service available")
)

Static errors for err113 compliance

Functions

This section is empty.

Types

type AWSIAMAuthConfig added in v1.0.10

type AWSIAMAuthConfig struct {
	// Enabled indicates whether AWS IAM authentication is enabled
	Enabled bool `json:"enabled" yaml:"enabled" env:"AWS_IAM_AUTH_ENABLED"`

	// Region specifies the AWS region for the RDS instance
	Region string `json:"region" yaml:"region" env:"AWS_IAM_AUTH_REGION"`

	// DBUser specifies the database username for IAM authentication
	DBUser string `json:"db_user" yaml:"db_user" env:"AWS_IAM_AUTH_DB_USER"`

	// TokenRefreshInterval specifies how often to refresh the IAM token (in seconds)
	// Default is 10 minutes (600 seconds), tokens expire after 15 minutes
	TokenRefreshInterval int `json:"token_refresh_interval" yaml:"token_refresh_interval" env:"AWS_IAM_AUTH_TOKEN_REFRESH"`
}

AWSIAMAuthConfig represents AWS IAM authentication configuration

type AWSIAMTokenProvider added in v1.0.10

type AWSIAMTokenProvider struct {
	// contains filtered or unexported fields
}

AWSIAMTokenProvider manages AWS IAM authentication tokens for RDS

func NewAWSIAMTokenProvider added in v1.0.10

func NewAWSIAMTokenProvider(authConfig *AWSIAMAuthConfig) (*AWSIAMTokenProvider, error)

NewAWSIAMTokenProvider creates a new AWS IAM token provider

func (*AWSIAMTokenProvider) BuildDSNWithIAMToken added in v1.0.10

func (p *AWSIAMTokenProvider) BuildDSNWithIAMToken(ctx context.Context, originalDSN string) (string, error)

BuildDSNWithIAMToken takes a DSN and replaces the password with the IAM token

func (*AWSIAMTokenProvider) GetToken added in v1.0.10

func (p *AWSIAMTokenProvider) GetToken(ctx context.Context, endpoint string) (string, error)

GetToken returns the current valid IAM token, refreshing if necessary

func (*AWSIAMTokenProvider) StartTokenRefresh added in v1.0.10

func (p *AWSIAMTokenProvider) StartTokenRefresh(ctx context.Context, endpoint string)

StartTokenRefresh starts a background goroutine to refresh tokens periodically

func (*AWSIAMTokenProvider) StopTokenRefresh added in v1.0.10

func (p *AWSIAMTokenProvider) StopTokenRefresh()

StopTokenRefresh stops the background token refresh

type Config

type Config struct {
	// Connections contains all defined database connections
	Connections map[string]*ConnectionConfig `json:"connections" yaml:"connections"`

	// Default specifies the name of the default connection
	Default string `json:"default" yaml:"default" env:"DEFAULT_DB_CONNECTION"`
}

Config represents database module configuration

func (*Config) GetInstanceConfigs added in v1.0.14

func (c *Config) GetInstanceConfigs() map[string]interface{}

GetInstanceConfigs returns the connections map for instance-aware configuration

func (*Config) Validate added in v1.0.17

func (c *Config) Validate() error

Validate implements ConfigValidator interface

type ConnectionConfig

type ConnectionConfig struct {
	// Driver specifies the database driver to use (e.g., "postgres", "mysql")
	Driver string `json:"driver" yaml:"driver" env:"DRIVER"`

	// DSN is the database connection string
	DSN string `json:"dsn" yaml:"dsn" env:"DSN"`

	// MaxOpenConnections sets the maximum number of open connections to the database
	MaxOpenConnections int `json:"max_open_connections" yaml:"max_open_connections" env:"MAX_OPEN_CONNECTIONS"`

	// MaxIdleConnections sets the maximum number of idle connections in the pool
	MaxIdleConnections int `json:"max_idle_connections" yaml:"max_idle_connections" env:"MAX_IDLE_CONNECTIONS"`

	// ConnectionMaxLifetime sets the maximum amount of time a connection may be reused (in seconds)
	ConnectionMaxLifetime int `json:"connection_max_lifetime" yaml:"connection_max_lifetime" env:"CONNECTION_MAX_LIFETIME"`

	// ConnectionMaxIdleTime sets the maximum amount of time a connection may be idle (in seconds)
	ConnectionMaxIdleTime int `json:"connection_max_idle_time" yaml:"connection_max_idle_time" env:"CONNECTION_MAX_IDLE_TIME"`

	// AWSIAMAuth contains AWS IAM authentication configuration
	AWSIAMAuth *AWSIAMAuthConfig `json:"aws_iam_auth,omitempty" yaml:"aws_iam_auth,omitempty"`
}

ConnectionConfig represents configuration for a single database connection

type DatabaseService

type DatabaseService interface {
	// Connect establishes the database connection
	Connect() error

	// Close closes the database connection
	Close() error

	// DB returns the underlying database connection
	DB() *sql.DB

	// Ping verifies the database connection is still alive
	Ping(ctx context.Context) error

	// Stats returns database statistics
	Stats() sql.DBStats

	// ExecContext executes a query without returning any rows
	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)

	// Exec executes a query without returning any rows (using default context)
	Exec(query string, args ...interface{}) (sql.Result, error)

	// PrepareContext prepares a statement for execution
	PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)

	// Prepare prepares a statement for execution (using default context)
	Prepare(query string) (*sql.Stmt, error)

	// QueryContext executes a query that returns rows
	QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)

	// Query executes a query that returns rows (using default context)
	Query(query string, args ...interface{}) (*sql.Rows, error)

	// QueryRowContext executes a query that returns a single row
	QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row

	// QueryRow executes a query that returns a single row (using default context)
	QueryRow(query string, args ...interface{}) *sql.Row

	// BeginTx starts a transaction
	BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)

	// Begin starts a transaction with default options
	Begin() (*sql.Tx, error)
}

DatabaseService defines the operations that can be performed with a database

func NewDatabaseService

func NewDatabaseService(config ConnectionConfig) (DatabaseService, error)

NewDatabaseService creates a new database service from configuration

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module represents the database module and implements the modular.Module interface. It manages multiple database connections and provides services for database access.

The module supports:

  • Multiple named database connections
  • Automatic connection health monitoring
  • Default connection selection
  • Service abstraction for easier testing
  • Instance-aware configuration

func NewModule

func NewModule() *Module

NewModule creates a new database module instance. The returned module must be registered with the application before use.

Example:

dbModule := database.NewModule()
app.RegisterModule(dbModule)

func (*Module) Dependencies added in v1.0.8

func (m *Module) Dependencies() []string

Dependencies returns the names of modules this module depends on. The database module has no dependencies, making it suitable as a foundation module that other modules can depend on.

func (*Module) GetConnection

func (m *Module) GetConnection(name string) (*sql.DB, bool)

GetConnection returns a database connection by name. This method allows access to specific named database connections that were configured in the module's configuration.

Example:

if db, exists := dbModule.GetConnection("primary"); exists {
    // Use the primary database connection
}

func (*Module) GetConnections

func (m *Module) GetConnections() []string

GetConnections returns a list of all available connection names. This is useful for discovery and diagnostic purposes.

func (*Module) GetDefaultConnection

func (m *Module) GetDefaultConnection() *sql.DB

GetDefaultConnection returns the default database connection. The default connection is determined by the "default" field in the configuration. If no default is configured or the named connection doesn't exist, this method will return any available connection.

Returns nil if no connections are available.

func (*Module) GetDefaultService added in v1.0.8

func (m *Module) GetDefaultService() DatabaseService

GetDefaultService returns the default database service. Similar to GetDefaultConnection, but returns a DatabaseService interface that provides additional functionality beyond the raw sql.DB.

func (*Module) GetService added in v1.0.8

func (m *Module) GetService(name string) (DatabaseService, bool)

GetService returns a database service by name. Database services provide a higher-level interface than raw database connections, including connection management and additional utilities.

func (*Module) Init

func (m *Module) Init(app modular.Application) error

Init initializes the database module and establishes database connections. This method loads the configuration, validates connection settings, and establishes connections to all configured databases.

Initialization process:

  1. Load configuration from the "database" section
  2. Validate connection configurations
  3. Create database services for each connection
  4. Test initial connectivity

func (*Module) Name

func (m *Module) Name() string

Name returns the name of the module. This name is used for dependency resolution and configuration section lookup.

func (*Module) ProvidesServices

func (m *Module) ProvidesServices() []modular.ServiceProvider

ProvidesServices declares services provided by this module. The database module provides:

  • database.manager: Module instance for direct database management
  • database.service: Default database service for convenient access

Other modules can depend on these services to access database functionality.

func (*Module) RegisterConfig

func (m *Module) RegisterConfig(app modular.Application) error

RegisterConfig registers the module's configuration structure. The database module uses instance-aware configuration to support environment-specific database connection settings.

Configuration structure:

  • Default: name of the default connection to use
  • Connections: map of connection names to their configurations

Environment variables:

DB_<CONNECTION_NAME>_DRIVER, DB_<CONNECTION_NAME>_DSN, etc.

func (*Module) RequiresServices

func (m *Module) RequiresServices() []modular.ServiceDependency

RequiresServices declares services required by this module. The database module is self-contained and doesn't require services from other modules.

func (*Module) Start

func (m *Module) Start(ctx context.Context) error

Start starts the database module and verifies connectivity. This method performs health checks on all database connections to ensure they are ready for use by other modules.

func (*Module) Stop

func (m *Module) Stop(ctx context.Context) error

Stop stops the database module and closes all connections. This method gracefully closes all database connections and services, ensuring proper cleanup during application shutdown.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL