nagaya

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2024 License: MIT Imports: 13 Imported by: 0

README

CI PkgGoDev

nagaya

Nagaya provides database multi-tenancy utility for Go web applications.

It is highly inspired by apartment, so you can use nagaya like apartment.

Nagaya is a style of apartments which typical for Edo period in Japan.

Install

go get github.com/aereal/nagaya

Synopsis

import (
  "database/sql"
  "net/http"

  "github.com/aereal/nagaya"
)

func _() {
  var db *sql.DB
  manager := nagaya.NewStd(db)
  yourHandler := http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
    // obtain a database connection which bound for the current tenant.
    conn, _ := manager.ObtainConnection(r.Context())
    // run queries against the current tenant.
    _, _ = conn.GetContext(r.Context(), "select * from users")
  })
  // Nagaya middleware sets current tenant to the request context.
  // So you can obtain database connection bound for the current tenant in your HTTP handler via the context.
  mw := nagaya.Middleware(
    manager,
    nagaya.GetTenantFromHeader("tenant-id"), // this option tells the Nagaya uses `tenant-id` request header value as the current tenant
  )
  _ = mw(yourHandler)
}

Developemnt and testing

docker compose up -d
port="$(docker compose ps --format json | jq '[(.Publishers[] | select(.TargetPort == 3306))][0].PublishedPort')"
export TEST_DB_DSN="root@tcp(127.0.0.1:${port})/tenant_default"

License

See LICENSE file.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoTenantBound is an error represents no tenant bound for the context.
	ErrNoTenantBound = errors.New("no tenant bound for the context")
	// ErrNoConnectionBound is an error represents no DB connection obtained for the context.
	ErrNoConnectionBound = errors.New("no DB connection bound for the context")
	// ErrNoTenantChange indicates the nagaya no need to change tenant.
	ErrNoTenantChange = errors.New("no tenant change")
)
View Source
var (
	KeyTenant    = attribute.Key("nagaya.tenant")
	KeyRequestID = attribute.Key("nagaya.request_id")
)

Functions

func ContextWithRequestID added in v0.5.0

func ContextWithRequestID(ctx context.Context, id string) context.Context

func Middleware

func Middleware[DB DBish, Conn Connish](n *Nagaya[DB, Conn], opts ...MiddlewareOption) func(http.Handler) http.Handler

Middleware returns a middleware function that determines target tenant and obtain the database connection against the tenant.

The consumer must get the obtained connection via Nagaya.ObtainConnection method and use it to access the database.

func WithTenant

func WithTenant(ctx context.Context, tenant Tenant) context.Context

WithTenant returns new context that contains given tenant.

func WithTimeout

func WithTimeout(dur time.Duration) interface {
	MiddlewareOption
	BindConnectionOption
}

WithTimeout sets the how long wait for a tenant change.

func WithTracerProvider added in v0.3.0

func WithTracerProvider(tp trace.TracerProvider) interface {
	NewOption
	MiddlewareOption
}

Types

type BindConnectionOption added in v0.5.0

type BindConnectionOption interface {
	// contains filtered or unexported methods
}

type ChangeTenantError

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

ChangeTenantError is an error type represents the failure of changing current tenant.

func (*ChangeTenantError) Error

func (e *ChangeTenantError) Error() string

func (*ChangeTenantError) Tenant

func (e *ChangeTenantError) Tenant() Tenant

Tenant returns a tenant to be switched.

func (*ChangeTenantError) Unwrap

func (e *ChangeTenantError) Unwrap() error

type Connish

type Connish interface {
	BeginTx(context.Context, *sql.TxOptions) (*sql.Tx, error)
	PingContext(context.Context) error
	ExecContext(context.Context, string, ...any) (sql.Result, error)
	PrepareContext(context.Context, string) (*sql.Stmt, error)
	QueryContext(context.Context, string, ...any) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...any) *sql.Row
	Close() error
}

type DBish

type DBish interface {
	BeginTx(context.Context, *sql.TxOptions) (*sql.Tx, error)
	PingContext(context.Context) error
	ExecContext(context.Context, string, ...any) (sql.Result, error)
	PrepareContext(context.Context, string) (*sql.Stmt, error)
	QueryContext(context.Context, string, ...any) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...any) *sql.Row
	Close() error
	Stats() sql.DBStats
	SetConnMaxIdleTime(time.Duration)
	SetConnMaxLifetime(time.Duration)
	SetMaxIdleConns(int)
	SetMaxOpenConns(int)
}

type DecideTenantFn added in v0.4.0

type DecideTenantFn func(*http.Request) TenantDecisionResult

type ErrorHandler

type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)

type GenerateRequestIDError

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

GenerateRequestIDError is an error type represents the failure of generating ID of the current request.

func (*GenerateRequestIDError) Error

func (e *GenerateRequestIDError) Error() string

func (*GenerateRequestIDError) Unwrap

func (e *GenerateRequestIDError) Unwrap() error

type GetConnFn

type GetConnFn[DB DBish, Conn Connish] func(ctx context.Context, db DB) (Conn, error)

GetConnFn is a function type returns new database connection from the DB.

type MiddlewareOption

type MiddlewareOption interface {
	// contains filtered or unexported methods
}

MiddlewareOption applies a configuration option value to a middleware.

func DecideTenantFromHeader added in v0.4.0

func DecideTenantFromHeader(headerName string) MiddlewareOption

DecideTenantFromHeader tells the middleware to use given header value to decide the tenant.

func GetTenantFromHeader deprecated

func GetTenantFromHeader(headerName string) MiddlewareOption

GetTenantFromHeader tells the middleware get the tenant from the request header.

Deprecated: use DecideTenantFromHeader

func WithChangeTenantErrorHandler

func WithChangeTenantErrorHandler(handler ErrorHandler) MiddlewareOption

func WithDecideTenantFn added in v0.4.0

func WithDecideTenantFn(fn DecideTenantFn) MiddlewareOption

WithDecideTenantFn tells the middleware to use given function to decide the tenant.

func WithErrorHandler

func WithErrorHandler(handler ErrorHandler) MiddlewareOption

func WithGenerateRequestIDErrorHandler

func WithGenerateRequestIDErrorHandler(handler ErrorHandler) MiddlewareOption

func WithGetTenantFn deprecated

func WithGetTenantFn(fn func(r *http.Request) (Tenant, bool)) MiddlewareOption

WithGetTenantFn tells the middleware get the tenant using given function.

Deprecated: use WithDecideTenantFn

func WithNoTenantBoundErrorHandler

func WithNoTenantBoundErrorHandler(handler ErrorHandler) MiddlewareOption

func WithObtainConnectionErrorHandler

func WithObtainConnectionErrorHandler(handler ErrorHandler) MiddlewareOption

func WithRequestIDGenerator

func WithRequestIDGenerator(gen RequestIDGenerator) MiddlewareOption

type Nagaya

type Nagaya[DB DBish, Conn Connish] struct {
	// contains filtered or unexported fields
}

func New

func New[DB DBish, Conn Connish](db DB, getConn GetConnFn[DB, Conn], opts ...NewOption) *Nagaya[DB, Conn]

func NewStd

func NewStd(db *sql.DB, opts ...NewOption) *Nagaya[*sql.DB, *sql.Conn]

func (*Nagaya[DB, Conn]) BindConnection added in v0.5.0

func (n *Nagaya[DB, Conn]) BindConnection(ctx context.Context, tenant Tenant, opts ...BindConnectionOption) (c Conn, err error)

BindConnection returns a new connection from the DB that bound for given tenant.

Usually the users should use Middleware.

func (*Nagaya[DB, Conn]) ObtainConnection

func (n *Nagaya[DB, Conn]) ObtainConnection(ctx context.Context) (conn Conn, err error)

ObtainConnection returns a database connection bound to the current tenant.

[BindConnection] must be called before this method called. Almost users just use Middleware that calls [BindConnection].

func (*Nagaya[DB, Conn]) ReleaseConnection added in v0.5.0

func (n *Nagaya[DB, Conn]) ReleaseConnection(requestID string)

ReleaseConnection marks the current request's connection is ready to discard.

This method does not call sql.Conn.Close, it is caller's responsibility.

type NewOption added in v0.3.0

type NewOption interface {
	// contains filtered or unexported methods
}

type ObtainConnectionError

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

ObtainConnectionError is an error type represents the failure of obtaining DB connection.

func (*ObtainConnectionError) Error

func (e *ObtainConnectionError) Error() string

func (*ObtainConnectionError) Unwrap

func (e *ObtainConnectionError) Unwrap() error

type RequestIDGenerator

type RequestIDGenerator interface {
	GenerateID(ctx context.Context, r *http.Request) (string, error)
}

type RequestIDGeneratorFunc

type RequestIDGeneratorFunc func(ctx context.Context, r *http.Request) (string, error)

func (RequestIDGeneratorFunc) GenerateID

func (f RequestIDGeneratorFunc) GenerateID(ctx context.Context, r *http.Request) (string, error)

type Tenant

type Tenant string

Tenant is an identifier of resource subset stored in the shared database.

func TenantFromContext

func TenantFromContext(ctx context.Context) (Tenant, bool)

TenantFromContext extracts a tenant in the context.

If no tenant is bound for the context, the second return value is a false.

type TenantDecision added in v0.4.0

type TenantDecision int

TenantDecision indicates whether a tenant is changed, cannot be changed due to some error, or unchanged.

const (
	// TenantDecisionNoChange will use default tenant.
	TenantDecisionNoChange TenantDecision = iota
	// TenantDecisionError has an intention to change the tenant but failed to determine it.
	TenantDecisionError
	// TenantDecisionChangeTenant will change a tenant.
	TenantDecisionChangeTenant
)

type TenantDecisionResult added in v0.4.0

type TenantDecisionResult interface {
	Decision() TenantDecision
	DecideTenant() (Tenant, error)
	// contains filtered or unexported methods
}

TenantDecisionResult conveys a TenantDecision and a tenant.

type TenantDecisionResultChangeTenant added in v0.4.0

type TenantDecisionResultChangeTenant struct{ Tenant Tenant }

func (*TenantDecisionResultChangeTenant) DecideTenant added in v0.4.0

func (r *TenantDecisionResultChangeTenant) DecideTenant() (Tenant, error)

func (TenantDecisionResultChangeTenant) Decision added in v0.4.0

type TenantDecisionResultError added in v0.4.0

type TenantDecisionResultError struct{ Err error }

func (*TenantDecisionResultError) DecideTenant added in v0.4.0

func (r *TenantDecisionResultError) DecideTenant() (Tenant, error)

func (TenantDecisionResultError) Decision added in v0.4.0

type TenantDecisionResultNoChange added in v0.4.0

type TenantDecisionResultNoChange struct{}

func (TenantDecisionResultNoChange) DecideTenant added in v0.4.0

func (TenantDecisionResultNoChange) DecideTenant() (Tenant, error)

func (TenantDecisionResultNoChange) Decision added in v0.4.0

Jump to

Keyboard shortcuts

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