Skip to content

Latest commit

 

History

History

fxsql

Fx SQL Module

ci go report codecov Deps PkgGoDev

Fx module for sql.

Installation

go get github.com/ankorstore/yokai/fxsql

Features

This module provides a *sql.DB to your Fx application with:

  • automatic SQL requests logging and tracing
  • possibility to define and apply database migrations (based on Goose)
  • possibility to register database hooks
  • possibility to register and execute database seeds

Documentation

Dependencies

This module is intended to be used alongside:

Loading

To load the module in your Fx application:

package main

import (
	"database/sql"
	
	"github.com/ankorstore/yokai/fxconfig"
	"github.com/ankorstore/yokai/fxlog"
	"github.com/ankorstore/yokai/fxsql"
	"github.com/ankorstore/yokai/fxtrace"
	"go.uber.org/fx"
)

func main() {
	fx.New(
		fxconfig.FxConfigModule, // load the module dependencies
		fxlog.FxLogModule,
		fxtrace.FxTraceModule,
		fxsql.FxSQLModule,       // load the module
		fx.Invoke(func(db *sql.DB) {
			// use the DB
			res, _ := db.Exec(...)
		}),
	).Run()
}

Configuration

This module provides the possibility to configure the SQL driver:

Configuration reference:

# ./configs/config.yaml
app:
  name: app
  env: dev
  version: 0.1.0
  debug: false
modules:
  sql:
    driver: mysql                                               # database driver (empty by default)
    dsn: "user:password@tcp(localhost:3306)/db?parseTime=true"  # database DSN (empty by default)
    migrations: 
      path: db/migrations       # migrations path (empty by default)
      stdout: true              # to print in stdout the migration logs (disabled by default)
    log:
      enabled: true             # to enable SQL queries logging (disabled by default)
      level: debug              # to configure SQL queries logs level (debug by default)
      arguments: true           # to add SQL queries arguments to logs (disabled by default)
      exclude:                  # to exclude SQL operations from logging (empty by default)
        - "connection:ping"
    trace:
      enabled: true             # to enable SQL queries tracing (disabled by default)
      arguments: true           # to add SQL queries arguments to trace spans (disabled by default)
      exclude:                  # to exclude SQL operations from tracing (empty by default)
        - "connection:ping"

For security reasons, you should avoid to hardcode DSN sensible parts (like the password) in your config files, you can use the env vars placeholders instead:

# ./configs/config.yaml
modules:
  sql:
    driver: mysql
    dsn: "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/${MYSQL_DATABASE}?parseTime=True"

Available SQL operations:

  • connection:begin
  • connection:begin-tx
  • connection:exec
  • connection:exec-context
  • connection:query
  • connection:query-context
  • connection:prepare
  • connection:prepare-context
  • connection:ping
  • connection:reset-session
  • connection:close
  • statement:exec
  • statement:exec-context
  • statement:query
  • statement:query-context
  • transaction:commit
  • transaction:rollback

Migrations

This module provides the possibility to run your DB schemas migrations, using Goose under the hood.

First, configure the path for your migration files:

# ./configs/config.yaml
modules:
  sql:
    migrations: db/migrations

You can then create a migration file in this path.

For example, db/migrations/00001_create_foo_table.sql:

-- +goose Up
CREATE TABLE foo (
    id  INTEGER NOT NULL PRIMARY KEY,
    bar VARCHAR(255)
);

-- +goose Down
DROP TABLE IF EXISTS foo;

You can then run the migrations with RunFxSQLMigration, by specifying a migration command:

package main

import (
	"database/sql"

	"github.com/ankorstore/yokai/fxconfig"
	"github.com/ankorstore/yokai/fxlog"
	"github.com/ankorstore/yokai/fxsql"
	"github.com/ankorstore/yokai/fxtrace"
	"go.uber.org/fx"
)

func main() {
	fx.New(
		fxconfig.FxConfigModule,       // load the module dependencies
		fxlog.FxLogModule,
		fxtrace.FxTraceModule,
		fxsql.FxSQLModule,             // load the module
		fxsql.RunFxSQLMigration("up"), // run migration command "up"
	).Run()
}

Available migration commands:

  • up: migrate the DB to the most recent version available
  • up-by-one: migrate the DB up by 1
  • up-to VERSION: migrate the DB to a specific VERSION
  • down: roll back the version by 1
  • down-to VERSION: roll back to a specific VERSION
  • redo: re-run the latest migration
  • reset: roll back all migrations
  • status: dump the migration status for the current DB
  • version: print the current version of the database
  • create NAME [sql|go]: creates new migration file with the current timestamp
  • fix: apply sequential ordering to migrations
  • validate: check migration files without running them

If you want to automatically shut down your Fx application after the migrations, you can use RunFxSQLMigrationAndShutdown.

Seeds

This module provides the possibility to register several Seed implementations to seed the database.

This is done via:

  • the AsSQLSeed() function to register seeds
  • the RunFxSQLSeeds() function to execute seeds
package main

import (
	"context"
	"database/sql"

	"github.com/ankorstore/yokai/fxconfig"
	"github.com/ankorstore/yokai/fxlog"
	"github.com/ankorstore/yokai/fxsql"
	"github.com/ankorstore/yokai/fxtrace"
	"go.uber.org/fx"
)

// example SQL seed
type ExampleSeed struct{}

func NewExampleSeed() *ExampleSeed {
	return &ExampleSeed{}
}

func (s *ExampleSeed) Name() string {
	return "example-seed"
}

func (s *ExampleSeed) Run(ctx context.Context, db *sql.DB) error {
  _, err := db.ExecContext(ctx, "INSERT INTO foo (bar) VALUES (?)", "baz")

  return err
}

// usage
func main() {
	fx.New(
		fxconfig.FxConfigModule,          // load the module dependencies
		fxlog.FxLogModule,
		fxtrace.FxTraceModule,
		fxsql.FxSQLModule,                // load the module
		fxsql.AsSQLHook(NewExampleSeed),  // register the ExampleSeed
		fxsql.RunFxSQLSeeds(),            // run all registered seeds
	).Run()
}

You can also use AsSQLSeeds() to register several seeds at once.

You can also call for example RunFxSQLSeeds("example-seed", "other-seed") to run specific seeds, in provided order.

The dependencies of your seeds constructors will be autowired.

Hooks

This module provides the possibility to register several Hook implementations to extend the logic around the SQL operations.

This is done via the AsSQLHook() function:

package main

import (
	"context"

	"github.com/ankorstore/yokai/fxconfig"
	"github.com/ankorstore/yokai/fxlog"
	"github.com/ankorstore/yokai/fxsql"
	"github.com/ankorstore/yokai/fxtrace"
	"github.com/ankorstore/yokai/sql"
	"go.uber.org/fx"
)

// example SQL hook
type ExampleHook struct{}

func NewExampleHook() *ExampleHook {
	return &ExampleHook{}
}

func (h *ExampleHook) Before(ctx context.Context, event *sql.HookEvent) context.Context {
	// before SQL operation logic

	return ctx
}

func (h *ExampleHook) After(ctx context.Context, event *sql.HookEvent) {
	// after SQL operation logic
}

// usage
func main() {
	fx.New(
		fxconfig.FxConfigModule,         // load the module dependencies
		fxlog.FxLogModule,
		fxtrace.FxTraceModule,
		fxsql.FxSQLModule,               // load the module
		fxsql.AsSQLHook(NewExampleHook), // register the ExampleHook
	).Run()
}

You can also use AsSQLHooks() to register several hooks at once.

The dependencies of your hooks constructors will be autowired.

Testing

This module supports the sqlite driver, allowing the usage of an in-memory database, avoiding your tests to require a real database instance to run.

In your testing configuration:

# ./configs/config.test.yaml
modules:
  sql:
    driver: sqlite
    dsn: ":memory:"

In test mode, the module won't automatically close the database connection on shutdown, to allow database manipulation after the RunTest() execution.

You can find tests examples in this module own tests.