multitenancy

package module
v8.0.0-...-33749c5 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 2025 License: Apache-2.0 Imports: 9 Imported by: 2

README

gorm-multitenancy

Mentioned in Awesome Go Go Reference Release Go Report Card codecov Tests GitHub issues License

GORM Multitenancy

Photo by Ashley McNamara, via ashleymcnamara/gophers (CC BY-NC-SA 4.0)

Overview

Gorm-multitenancy provides a Go framework for building multi-tenant applications, streamlining tenant management and model migrations. It abstracts multitenancy complexities through a unified, database-agnostic API compatible with GORM.

Multitenancy Approaches

There are three common approaches to multitenancy in a database:

  • Shared database, shared schema
  • Shared database, separate schemas
  • Separate databases

Depending on the database in use, this package utilizes either the "shared database, separate schemas" or "separate databases" strategy, ensuring a smooth integration with your existing database configuration through the provision of tailored drivers.

Features

  • GORM Integration: Simplifies GORM usage in multi-tenant environments, offering a unified API alongside direct access to driver-specific APIs for flexibility.
  • Custom Database Drivers: Enhances existing drivers for easy multitenancy setup without altering initialization.
  • HTTP Middleware: Provides middleware for easy tenant context management in web applications.

Supported Databases

Database Approach
PostgreSQL Shared database, separate schemas
MySQL Separate databases

Router Integration

Installation

Install the core package:

go get -u github.com/thisaftermath/gorm-multitenancy/v8

Install the database-specific driver:

# PostgreSQL
go get -u github.com/thisaftermath/gorm-multitenancy/postgres/v8

# MySQL
go get -u github.com/thisaftermath/gorm-multitenancy/mysql/v8

Optionally, install the router-specific middleware:

# Echo
go get -u github.com/thisaftermath/gorm-multitenancy/middleware/echo/v8

# Gin
go get -u github.com/thisaftermath/gorm-multitenancy/middleware/gin/v8

# Iris
go get -u github.com/thisaftermath/gorm-multitenancy/middleware/iris/v8

# Net/HTTP
go get -u github.com/thisaftermath/gorm-multitenancy/middleware/nethttp/v8

Getting Started

Check out the pkg.go.dev documentation for comprehensive guides and API references.

Running the Example Application

For a practical demonstration, you can run the example application. It showcases various configurations and usage scenarios.

Contributing

All contributions are welcome! See the Contributing Guide for more details.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Documentation

Overview

Package multitenancy provides a Go framework for building multi-tenant applications, streamlining tenant management and model migrations. It abstracts multitenancy complexities through a unified, database-agnostic API compatible with GORM.

The framework supports two primary multitenancy strategies:

  • Shared Database, Separate Schemas: This approach allows for data isolation and schema customization per tenant within a single database instance, simplifying maintenance and resource utilization.
  • Separate Databases: This strategy ensures complete data isolation by utilizing separate databases for each tenant, making it suitable for applications with stringent data security requirements.

Usage

The following sections provide an overview of the package's features and usage instructions for implementing multitenancy in Go applications.

Opening a Database Connection

The package supports multitenancy for both PostgreSQL and MySQL databases, offering three methods for establishing a new database connection with multitenancy support:

OpenDB allows opening a database connection using a URL-like DSN string, providing a flexible and easy way to switch between drivers. This method abstracts the underlying driver mechanics, offering a straightforward connection process and a unified, database-agnostic API through the returned *DB instance, which embeds the gorm.DB instance.

For constructing the DSN string, refer to the driver-specific documentation for the required parameters and formats.

import (
    _ "github.com/thisaftermath/gorm-multitenancy/<driver>/v8"
    multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
)

func main() {
    url := "<driver>://user:password@host:port/dbname"
    db, err := multitenancy.OpenDB(context.Background(), url)
    if err != nil {...}
	db.RegisterModels(ctx, ...) // Access to a database-agnostic API with GORM features
}

This approach is useful for applications that need to dynamically switch between different database drivers or configurations without changing the codebase.

Postgres:

import (
    _ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
    multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
)

func main() {
    url := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
    db, err := multitenancy.OpenDB(context.Background(), url)
    if err != nil {...}
}

MySQL:

import (
    _ "github.com/thisaftermath/gorm-multitenancy/mysql/v8"
    multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
)

func main() {
    url := "mysql://user:password@tcp(localhost:3306)/dbname"
    db, err := multitenancy.OpenDB(context.Background(), url)
    if err != nil {...}
}

Approach 2: Unified API

Open with a supported driver offers a unified, database-agnostic API for managing tenant-specific and shared data, embedding the gorm.DB instance. This method allows developers to initialize and configure the dialect themselves before opening the database connection, providing granular control over the database connection and configuration. It facilitates seamless switching between database drivers while maintaining access to GORM's full functionality.

import (
    multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
    "github.com/thisaftermath/gorm-multitenancy/<driver>/v8"
)

func main() {
    dsn := "<driver-specific DSN>"
    db, err := multitenancy.Open(<driver>.Open(dsn))
    if err != nil {...}
    db.RegisterModels(ctx, ...) // Access to a database-agnostic API with GORM features
}

Postgres:

import (
    multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
    "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
)

func main() {
    dsn := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
    db, err := multitenancy.Open(postgres.Open(dsn))
}

MySQL:

    import (
        multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
        "github.com/thisaftermath/gorm-multitenancy/mysql/v8"
    )

    func main() {
        dsn := "user:password@tcp(localhost:3306)/dbname"
		db, err := multitenancy.Open(mysql.Open(dsn))
    }

Approach 3: Direct Driver API

For direct access to the gorm.DB API and multitenancy features for specific tasks, this approach allows invoking driver-specific functions directly. It provides a lower level of abstraction, requiring manual management of database-specific operations. Prior to version 8, this was the only method available for using the package.

Postgres:

import (
    "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
    "gorm.io/gorm"
)

func main() {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    // Directly call driver-specific functions
    postgres.RegisterModels(db, ...)
}

MySQL:

import (
    "github.com/thisaftermath/gorm-multitenancy/mysql/v8"
    "gorm.io/gorm"
)

func main() {
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    // Directly call driver-specific functions
    mysql.RegisterModels(db, ...)
}

Declaring Models

All models must implement driver.TenantTabler, which extends GORM's Tabler interface. This extension allows models to define their table name and indicate whether they are shared across tenants.

Public Models:

These are models which are shared across tenants. driver.TenantTabler.TableName should return the table name prefixed with 'public.'. driver.TenantTabler.IsSharedModel should return true.

type Tenant struct { multitenancy.TenantModel}
func (Tenant) TableName() string   { return "public.tenants" } // note the 'public.' prefix
func (Tenant) IsSharedModel() bool { return true }

Tenant-Specific Models:

These models are specific to a single tenant and should not be shared across tenants. driver.TenantTabler.TableName should return the table name without any prefix. driver.TenantTabler.IsSharedModel should return false.

type Book struct {
	gorm.Model
	Title        string
	TenantSchema string
	Tenant       Tenant `gorm:"foreignKey:TenantSchema;references:SchemaName"`
}

func (Book) TableName() string   { return "books" } // no 'public.' prefix
func (Book) IsSharedModel() bool { return false }

Tenant Model:

This package provides a TenantModel struct that can be embedded in any public model that requires tenant scoping, enriching it with essential fields for managing tenant-specific information. This structure incorporates fields for the tenant's domain URL and schema name, facilitating the linkage of tenant-specific models to their respective schemas.

type Tenant struct { multitenancy.TenantModel }

func (Tenant) TableName() string   { return "public.tenants" }
func (Tenant) IsSharedModel() bool { return true }

Model Registration

Before performing any migrations or operations on tenant-specific models, the models must be registered with the DB instance using DB.RegisterModels.

import (
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
	_ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
)

func main() {
	db, err := multitenancy.OpenDB(ctx, dsn)
	if err != nil {...}
	db.RegisterModels(ctx, &Tenant{}, &Book{})
}

Postgres Adapter:

Use postgres.RegisterModels to register models.

import "github.com/thisaftermath/gorm-multitenancy/postgres/v8"

postgres.RegisterModels(db, &Tenant{}, &Book{})

MySQL Adapter:

Use mysql.RegisterModels to register models.

import "github.com/thisaftermath/gorm-multitenancy/mysql/v8"

mysql.RegisterModels(db, &Tenant{}, &Book{})

Migration Strategy

To ensure data integrity and schema isolation across tenants,gorm.DB.AutoMigrate has been disabled. Instead, use the provided shared and tenant-specific migration methods. driver.ErrInvalidMigration is returned if the `AutoMigrate` method is called directly.

Concurrent Migrations

To ensure tenant isolation and facilitate concurrent migrations, driver-specific locking mechanisms are used. These locks prevent concurrent migrations from interfering with each other, ensuring that only one migration process can run at a time for a given tenant. Consult the driver-specific documentation for more information.

Shared Model Migrations

After registering models, shared models are migrated using DB.MigrateSharedModels.

import (
	"context"
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
	_ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
)

func main() {
	db, err := multitenancy.OpenDB(ctx, dsn)
	if err != nil {...}
	db.RegisterModels(ctx, &Tenant{}, &Book{})
	db.MigrateSharedModels(ctx)
}

Postgres Adapter:

Use postgres.MigrateSharedModels to migrate shared models.

import "github.com/thisaftermath/gorm-multitenancy/postgres/v8"

postgres.MigrateSharedModels(db)

MySQL Adapter:

Use mysql.MigrateSharedModels to migrate shared models.

import "github.com/thisaftermath/gorm-multitenancy/mysql/v8"

mysql.MigrateSharedModels(db)

Tenant-Specific Model Migrations

After registering models, tenant-specific models are migrated using DB.MigrateTenantModels.

import (
	"context"
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
	_ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
)

func main() {
	db, err := multitenancy.OpenDB(ctx, dsn)
	if err != nil {...}
	db.RegisterModels(ctx, &Tenant{}, &Book{})
	db.MigrateSharedModels(ctx)
	// Assuming we have a tenant with schema name 'tenant1'
	db.MigrateTenantModels(ctx, "tenant1")
}

Postgres Adapter:

Use postgres.MigrateTenantModels to migrate tenant-specific models.

import "github.com/thisaftermath/gorm-multitenancy/postgres/v8"

postgres.MigrateTenantModels(db, "tenant1")

MySQL Adapter:

Use mysql.MigrateTenantModels to migrate tenant-specific models.

import "github.com/thisaftermath/gorm-multitenancy/mysql/v8"

mysql.MigrateTenantModels(db, "tenant1")

Offboarding Tenants

When a tenant is removed from the system, the tenant-specific schema and associated tables should be cleaned up using DB.OffboardTenant.

import (
	"context"
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
	_ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
)

func main() {
	db, err := multitenancy.OpenDB(ctx, dsn)
	if err != nil {...}
	db.RegisterModels(ctx, &Tenant{}, &Book{})
	db.MigrateSharedModels(ctx)
	// Assuming we have a tenant with schema name 'tenant1'
	db.MigrateTenantModels(ctx, "tenant1")
	db.OffboardTenant(ctx, "tenant1") // Drop the tenant schema and associated tables
}

Postgres Adapter:

Use postgres.DropSchemaForTenant to offboard a tenant.

import "github.com/thisaftermath/gorm-multitenancy/postgres/v8"

postgres.DropSchemaForTenant(db, "tenant1")

MySQL Adapter:

Use mysql.DropDatabaseForTenant to offboard a tenant.

import "github.com/thisaftermath/gorm-multitenancy/mysql/v8"

mysql.DropDatabaseForTenant(db, "tenant1")

Tenant Context Configuration

DB.UseTenant configures the database for operations specific to a tenant, abstracting database-specific operations for tenant context configuration. This method returns a reset function to revert the database context and an error if the operation fails.

import (
	"context"
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
	_ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
)

func main() {
	db, err := multitenancy.OpenDB(ctx, dsn)
	if err != nil {...}
	db.RegisterModels(ctx, &Tenant{}, &Book{})
	db.MigrateSharedModels(ctx)
	// Assuming we have a tenant with schema name 'tenant1'
	reset, err := db.UseTenant(ctx, "tenant1")
	if err != nil {...}
	defer reset() // reset to the default search path
	// ... do operations with the search path set to 'tenant1'
	db.Create(&Book{Title: "The Great Gatsby"})
	db.Find(&Book{})
	db.Delete(&Book{})
}

Postgres Adapter:

Use postgres.SetSearchPath to set the search path for a tenant.

import "github.com/thisaftermath/gorm-multitenancy/postgres/v8"

reset, err := postgres.SetSearchPath(ctx, db, "tenant1")
if err != nil {...}
defer reset() // reset to the default search path
db.Create(&Book{Title: "The Great Gatsby"})

MySQL Adapter:

Use mysql.UseDatabase function to set the database for a tenant.

import "github.com/thisaftermath/gorm-multitenancy/mysql/v8"

reset, err := mysql.UseDatabase(ctx, db, "tenant1")
if err != nil {...}
defer reset() // reset to the default database
db.Create(&Book{Title: "The Great Gatsby"})

Foreign Key Constraints

For the most part, foreign key constraints work as expected, but there are some restrictions and considerations to keep in mind when working with multitenancy. The following guidelines outline the supported relationships between tables:

Between Tables in the Public Schema:

  • No foreign key constraints restrictions
  • E.g., `public.events` -> `public.locations`.

From Public Schema Tables to Tenant-Specific Tables:

  • Avoid foreign key constraints to maintain isolation and integrity.
  • E.g., `public.users` should not reference `orders` in a tenant-specific schema.

From Tenant-Specific Tables to Public Schema Tables:

  • Allowed, enabling references to shared resources.
  • E.g., `invoices` in a tenant schema -> `public.payment_methods`.

Between Tenant-Specific Tables:

  • Constraints allowed within the same tenant schema for encapsulation.
  • E.g., `projects` -> `employees` within a tenant's schema.

Example

package main

import (
	"context"

	_ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
)

type Tenant struct{ multitenancy.TenantModel }

func (Tenant) TableName() string   { return "public.tenants" }
func (Tenant) IsSharedModel() bool { return true }

type Book struct {
	gorm.Model
	Title        string
	TenantSchema string
	Tenant       Tenant `gorm:"foreignKey:TenantSchema;references:SchemaName"`
}

func (Book) TableName() string   { return "books" }
func (Book) IsSharedModel() bool { return false }

func main() {
	ctx := context.Background()
	dsn := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
	db, err := multitenancy.OpenDB(ctx, dsn)
	if err != nil {
		panic(err)
	}

	if err := db.RegisterModels(ctx, &Tenant{}, &Book{}); err != nil {
		panic(err)
	}

	if err := db.MigrateSharedModels(ctx); err != nil {
		panic(err)
	}

	// Create and manage tenants as needed
	tenant := &Tenant{
		TenantModel: multitenancy.TenantModel{
			DomainURL:  "tenant1.example.com",
			SchemaName: "tenant1",
		},
	}
	// Create a tenant in the default public/shared schema
	if err := db.Create(tenant).Error; err != nil {
		panic(err)
	}

	// Migrate models under the tenant schema
	if err := db.MigrateTenantModels(ctx, tenant.SchemaName); err != nil {
		panic(err)
	}

	// Create a book under the tenant schema
	if err := createBookHandler(ctx, db, "The Great Gatsby", tenant.SchemaName); err != nil {
		panic(err)
	}

	// Drop the tenant schema and associated tables
	if err := db.OffboardTenant(ctx, tenant.SchemaName); err != nil {
		panic(err)
	}
}

func createBookHandler(ctx context.Context, tx *multitenancy.DB, title, tenantID string) error {
	// Set the tenant context for the current operation(s)
	reset, err := tx.UseTenant(ctx, tenantID)
	if err != nil {
		return err
	}
	defer reset()

	// Create a book under the tenant schema
	b := &Book{
		Title:        title,
		TenantSchema: tenantID,
	}
	// ... do operations with the search path set to <tenantID>
	return tx.Create(b).Error
}

See the example application for a more comprehensive demonstration of the framework's capabilities.

Security Considerations

Always sanitize input to prevent SQL injection vulnerabilities. This framework does not perform any validation on the database name or schema name parameters. It is the responsibility of the caller to ensure that these parameters are sanitized. To facilitate this, the framework provides the following utilities:

  • pkg/namespace/Validate to verify tenant names against all supported drivers (MySQL and PostgreSQL), ensuring both scheme and database name adhere to expected formats.
  • middleware/nethttp/ExtractSubdomain to extract subdomains from HTTP requests, which can be used to derive tenant names.

Design Strategy

For a detailed technical overview of SQL design strategies adopted by the framework, see the STRATEGY.md file.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Register

func Register(name string, adapter Adapter)

Register adds a new Adapter to the default registry under the specified driver name. It panics if an Adapter for the given driver name is already registered.

Types

type Adapter

type Adapter interface {
	// AdaptDB enhances an existing [gorm.DB] instance with additional functionalities and returns
	// a new [DB] instance. The returned DB instance should be used by a single goroutine at a time
	// to ensure thread safety and prevent concurrent access issues.
	AdaptDB(ctx context.Context, db *gorm.DB) (*DB, error)

	// OpenDBURL creates and returns a new [DB] instance using the provided URL. It returns an error
	// if the URL is invalid or the adapter fails to open the database. The URL must follow a standard
	// format, using the scheme to determine the driver.
	OpenDBURL(ctx context.Context, u *driver.URL, opts ...gorm.Option) (*DB, error)
}

Adapter defines an interface for enhancing gorm.DB instances with additional functionalities.

type DB

type DB struct {
	*gorm.DB
	// contains filtered or unexported fields
}

DB wraps a GORM DB connection, integrating support for multitenancy operations. It provides a unified interface for managing tenant-specific and shared data within a multi-tenant application, leveraging GORM's ORM capabilities for database operations.

func NewDB

func NewDB(d driver.DBFactory, tx *gorm.DB) *DB

NewDB creates a new DB instance using the provided driver.DBFactory and gorm.DB instance. This function is intended for use by custom Adapter implementations to create new instances of DB with multitenancy support. Not intended for direct use in application code.

func Open

func Open(dialector gorm.Dialector, opts ...gorm.Option) (*DB, error)

Open is a drop-in replacement for gorm.Open. It returns a new DB instance using the provided dialector and options.

MySQL:

import (
		"github.com/thisaftermath/gorm-multitenancy/mysql/v8"
		multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
 )

 func main() {
		dsn := "user:password@tcp(localhost:3306)/dbname?parseTime=True"
		db, err := multitenancy.Open(mysql.Open(dsn))
		if err != nil {...}
 }

PostgreSQL:

 import (
 	"github.com/thisaftermath/gorm-multitenancy/postgres/v8"
 	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
 )

func main() {
	dsn := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
	db, err := multitenancy.Open(postgres.Open(dsn))
	if err != nil {...}
}

func OpenDB

func OpenDB(ctx context.Context, urlstr string, opts ...gorm.Option) (*DB, error)

OpenDB creates a new DB instance using the provided URL string and returns it. The URL string must be in a standard URL format, as the scheme is used to determine the driver to use. Refer to the driver-specific documentation for more information on the URL format.

MySQL:

import (
	_ "github.com/thisaftermath/gorm-multitenancy/mysql/v8"
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
)

func main() {
	url := "mysql://user:password@tcp(localhost:3306)/dbname"
	db, err := multitenancy.OpenDB(context.Background(), url)
	if err != nil {...}
}

PostgreSQL:

import (
	_ "github.com/thisaftermath/gorm-multitenancy/postgres/v8"
	multitenancy "github.com/thisaftermath/gorm-multitenancy/v8"
)

func main() {
	url := "postgres://user:password@localhost:5432/dbname"
	db, err := multitenancy.OpenDB(context.Background(), url)
	if err != nil {...}
}

func (*DB) Begin

func (db *DB) Begin(opts ...*sql.TxOptions) *DB

Begin begins a transaction.

func (*DB) CurrentTenant

func (db *DB) CurrentTenant(ctx context.Context) string

CurrentTenant returns the identifier for the current tenant context or an empty string if no context is set.

func (*DB) Debug

func (db *DB) Debug() *DB

Debug starts debug mode.

func (*DB) MigrateSharedModels

func (db *DB) MigrateSharedModels(ctx context.Context) error

MigrateSharedModels migrates all registered shared/public models.

Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.

func (*DB) MigrateTenantModels

func (db *DB) MigrateTenantModels(ctx context.Context, tenantID string) error

MigrateTenantModels migrates all registered tenant-specific models for the specified tenant. This method is intended to be used when onboarding a new tenant or updating an existing tenant's schema to match the latest model definitions.

Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.

func (*DB) OffboardTenant

func (db *DB) OffboardTenant(ctx context.Context, tenantID string) error

OffboardTenant cleans up the database by dropping the tenant-specific schema and associated tables. This method is intended to be used after a tenant has been removed.

Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.

func (*DB) RegisterModels

func (db *DB) RegisterModels(ctx context.Context, models ...driver.TenantTabler) error

RegisterModels registers GORM model structs for multitenancy support, preparing models for tenant-specific operations.

Not safe for concurrent use by multiple goroutines. Call this method from your main function or during application initialization.

func (*DB) Session

func (db *DB) Session(config *gorm.Session) *DB

Session returns a new copy of the DB, which has a new session with the configuration.

func (*DB) Transaction

func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)

Transaction starts a transaction as a block, returns an error if there's any error within the block. If the function passed to tx returns an error, the transaction will be rolled back automatically, otherwise, the transaction will be committed.

func (*DB) UseTenant

func (db *DB) UseTenant(ctx context.Context, tenantID string) (reset func() error, err error)

UseTenant configures the database for operations specific to a tenant. A reset function is returned to revert the database context to its original state. This method is intended to be used when performing operations specific to a tenant, such as creating, updating, or deleting tenant-specific data.

Technically safe for concurrent use by multiple goroutines, but should not be used concurrently ito ensuring data integrity and schema isolation. Either use DB.WithTenant, or ensure that this method is called within a transaction or from its own database connection.

func (*DB) WithContext

func (db *DB) WithContext(ctx context.Context) *DB

WithContext sets the context for the DB.

func (*DB) WithTenant

func (db *DB) WithTenant(ctx context.Context, tenantID string, fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)

WithTenant executes the provided function within the context of a specific tenant, ensuring that the database operations are scoped to the tenant's schema. This method is intended to be used when performing a series of operations within a tenant context, such as creating, updating, or deleting tenant-specific data.

Safe for concurrent use by multiple goroutines ito ensuring data integrity and schema isolation.

type TenantModel

type TenantModel struct {
	// DomainURL is the domain URL of the tenant; same as [net/url.URL.Host].
	DomainURL string `json:"domainURL" mapstructure:"domainURL" gorm:"column:domain_url;uniqueIndex;size:128"`

	// SchemaName is the schema name of the tenant.
	//
	// Field-level permissions are restricted to read and create.
	//
	// The following constraints are applied:
	// 	- unique index
	// 	- size: 63
	//  - check: Not less than 3 characters long
	//
	// Note: Due to differences in regular expression support between PostgreSQL and MySQL,
	// complex validations based on patterns (e.g., ensuring the schema name does not start with 'pg_')
	// should be enforced at the application level or through database-specific features.
	//
	// Examples of valid schema names:
	// 	- "tenant1"
	// 	- "_tenant"
	// 	- "tenant_1"
	//
	// Examples of invalid schema names:
	// 	- "t" (less than 3 characters long)
	// 	- "tenant1!" (contains special characters)
	//
	SchemaName string `` /* 133-byte string literal not displayed */
}

TenantModel a basic GoLang struct which includes the following fields: DomainURL, SchemaName. It's intended to be embedded into any public model that needs to be scoped to a tenant, supporting both PostgreSQL and MySQL. It may be embedded into your model or you may build your own model without it.

For example:

type Tenant struct {
  multitenancy.TenantModel
}

type TenantPKModel

type TenantPKModel struct {
	// DomainURL is the domain URL of the tenant; same as [net/url.URL.Host].
	DomainURL string `json:"domainURL" mapstructure:"domainURL" gorm:"column:domain_url;uniqueIndex;size:128"`

	// SchemaName is the schema name of the tenant and the primary key of the model.
	// For details on the constraints and rules for this field, see [TenantModel.SchemaName].
	ID string `json:"id" mapstructure:"id" gorm:"column:id;primaryKey;uniqueIndex;->;<-:create;size:63;check:LENGTH(id) >= 3"`
}

TenantPKModel is identical to TenantModel but with SchemaName as a primary key field.

Directories

Path Synopsis
internal
testmodels
Package testmodels provides valid and invalid models for testing.
Package testmodels provides valid and invalid models for testing.
pkg
backoff
Package backoff provides exponential backoff retry logic.
Package backoff provides exponential backoff retry logic.
driver
Package driver provides the foundational interfaces for implementing multitenancy support within database systems.
Package driver provides the foundational interfaces for implementing multitenancy support within database systems.
drivertest
Package drivertest provides conformance tests for db implementations.
Package drivertest provides conformance tests for db implementations.
gmterrors
Package gmterrors provides error handling for the gorm-multitenancy package.
Package gmterrors provides error handling for the gorm-multitenancy package.
logext
Package logext provides a custom logger that logs messages to the provided output.
Package logext provides a custom logger that logs messages to the provided output.
migrator
Package migrator provides utilities for database migration management.
Package migrator provides utilities for database migration management.
namespace
Package namespace provides utilities for validating tenant names in a consistent manner across different database systems.
Package namespace provides utilities for validating tenant names in a consistent manner across different database systems.
scopes
Package scopes provides a set of predefined multitenancy scopes for GORM.
Package scopes provides a set of predefined multitenancy scopes for GORM.

Jump to

Keyboard shortcuts

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