multitenancy

package module
v5.11.0 Latest Latest
Warning

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

Go to latest
Published: May 9, 2024 License: Apache-2.0 Imports: 1 Imported by: 0

README

gorm-multitenancy

Go Reference Release Go Report Card Coverage Status Build GitHub issues License FOSSA Status

GORM Multitenancy

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

Table of Contents

Introduction

Gorm-multitenancy is a Go package that provides a framework for implementing multitenancy in your applications using GORM.

Multitenancy Approaches

There are three common approaches to multitenancy in a database:

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

This package implements the shared database, separate schemas approach to multitenancy, providing custom drivers for seamless integration with your existing database setup.

Features

  • GORM Integration: Uses the gorm ORM to manage the database, allowing for easy integration with your existing GORM setup.
  • Custom Database Drivers: Provides custom drivers to support multitenancy, allowing you to easily swap and change with your existing drivers with minimal initialization reconfiguration.
  • HTTP Middleware: Includes middleware for seamless integration with certain routers, enabling the retrieval of the tenant from the request and setting the tenant in context.

Database compatibility

Current supported databases are listed below. Pull requests for other drivers are welcome.

Router Integration

This package includes middleware that can be utilized with the routers listed below for seamless integration with the database drivers. While not a requirement, these routers are fully compatible with the provided middleware. Contributions for other routers are welcome.

Installation

go get -u github.com/bartventer/gorm-multitenancy/v5

Usage

PostgreSQL driver
Conventions
TableName

The driver uses the public schema for public models and the tenant-specific schema for tenant-specific models. All models must implement the gorm.Tabler interface.

Public Model

The table name for public models must be prefixed with public..

type Tenant struct {
    ID uint `gorm:"primaryKey"`
    // other fields...
}

func (Tenant) TableName() string {
    return "public.tenants"
}
Tenant Model

The table name for tenant-specific models must not contain any prefix.

type Book struct {
    ID uint `gorm:"primaryKey"`
    // other fields...
}

func (Book) TableName() string {
    return "books"
}

TenantTabler

All tenant-specific models must implement the TenantTabler interface, which classifies the model as a tenant-specific model. The TenantTabler interface is used to determine which models to migrate when calling MigratePublicSchema or CreateSchemaForTenant.

type Book struct {
    ID uint `gorm:"primaryKey"`
    // other fields...
}

func (Book) IsTenantTable() bool {
    return true
}
Model Registration

Models can be registered by either calling postgres.RegisterModels or when creating the dialect, by passing the models as variadic arguments to postgres.New or postgres.Open.

postgres.RegisterModels
db, err := gorm.Open(postgres.New(postgres.Config{
        DSN: "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable",
    }), &gorm.Config{})
postgres.RegisterModels(db, &Tenant{}, &Book{})

Further documentation here

postgres.New
import (
    "gorm.io/gorm"
    "github.com/bartventer/gorm-multitenancy/v5/drivers/postgres"
)

db, err := gorm.Open(postgres.New(postgres.Config{
        DSN: "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable",
    },&Tenant{}, &Book{}), &gorm.Config{})

Further documentation here

postgres.Open
import (
    "gorm.io/gorm"
    "github.com/bartventer/gorm-multitenancy/v5/drivers/postgres"
)

db, err := gorm.Open(postgres.Open(dsn, &Tenant{}, &Book{}), &gorm.Config{})

Further documentation here

Migrations

After all models have been registered, we can perform table migrations.

Public Tables

Call MigratePublicSchema to create the public schema and migrate all public models.

db.MigratePublicSchema()
Tenant/Schema Tables

Call CreateSchemaForTenant to create the schema for a tenant and migrate all tenant-specific models.

db.CreateSchemaForTenant(tenantSchemaName)
Dropping Schemas

Call DropSchemaForTenant to drop the schema and cascade all schema tables.

db.DropSchemaForTenant(tenantSchemaName)
Foreign Key Constraints

Conforming to the above conventions, foreign key constraints between public and tenant-specific models can be created just as if you were using a shared database and schema.

You can embed the postgres.TenantModel struct in your tenant model to add the necessary fields for the tenant model.

Then create a foreign key constraint between the public and tenant-specific models using the SchemaName field as the foreign key.

import (
    "github.com/bartventer/gorm-multitenancy/v5/drivers/postgres"
    "gorm.io/gorm"
)

type Tenant struct {
    gorm.Model
    postgres.TenantModel
}

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

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

func (Book) IsTenantTable() bool {
    return true
} 

func (Book) TableName() string {
    return "books"
}
Operations on Tenant-Specific Models
WithTenantSchema

Use the WithTenantSchema scope function when you want to perform operations on a tenant specific table, which may include foreign key constraints to a public schema table(s).

db.Scopes(WithTenantSchema(tenantID)).Find(&Book{})
SetSearchPath

Use the SetSearchPath function when the tenant schema table has foreign key constraints you want to access belonging to other tables in the same tenant schema (and or foreign key relations to public tables). This is for more complex operations but does add ~0.200ms overhead per operation.

import (
    pgschema "github.com/bartventer/gorm-multitenancy/v5/schema/postgres"
    "gorm.io/gorm"
)
db, resetSearchPath := pgschema.SetSearchPath(db, tenantSchemaName)
if db.Error() != nil {
    // handle error
}
defer resetSearchPath()
// No need to use any tenant scopes as the search path has been changed to the tenant's schema
db.Find(&Book{})
Basic Example

Here's a simplified example of how to use the gorm-multitenancy package with the PostgreSQL driver:


import (
    "gorm.io/gorm"
    "github.com/bartventer/gorm-multitenancy/v5/drivers/postgres"
)

// Tenant is a public model
type Tenant struct {
    gorm.Model
    postgres.TenantModel // Embed the TenantModel
}

// Implement the gorm.Tabler interface
func (t *Tenant) TableName() string {return "public.tenants"} // Note the public. prefix

// Book is a tenant specific model
type Book struct {
    gorm.Model
    Title        string
    TenantSchema string `gorm:"column:tenant_schema"`
    Tenant       Tenant `gorm:"foreignKey:TenantSchema;references:SchemaName"`
}

// Implement the gorm.Tabler interface
func (b *Book) TableName() string {return "books"} // Note the lack of prefix

// Implement the TenantTabler interface
func (b *Book) IsTenantTable() bool {return true} // This classifies the model as a tenant specific model

func main(){
    db, err := gorm.Open(postgres.New(postgres.Config{
        DSN: "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable",
    }), &gorm.Config{})
    if err != nil {
        panic(err)
    }

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

    if err := postgres.MigratePublicSchema(db); err != nil {
        panic(err)
    }

    tenant := &Tenant{
        TenantModel: postgres.TenantModel{
            DomainURL: "tenant1.example.com",
            SchemaName: "tenant1",
        },
    }
    if err := db.Create(tenant).Error; err != nil {
        panic(err)
    }

    if err := postgres.CreateSchemaForTenant(db, tenant.SchemaName); err != nil {
        panic(err)
    }

    if err := postgres.DropSchemaForTenant(db, tenant.SchemaName); err != nil {
        panic(err)
    }
}
Complete Examples

For more detailed examples, including how to use the middleware with different frameworks, please refer to the following:

Contributing

All contributions are welcome! Open a pull request to request a feature or submit a bug report.

License

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

FOSSA Status

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Migrator

type Migrator = gorm.Migrator

Migrator is an alias for gorm.Migrator.

type TenantTabler

type TenantTabler interface {
	// IsTenantTable returns true if the table is a tenant table
	IsTenantTable() bool
}

TenantTabler is the interface for tenant tables.

Directories

Path Synopsis
drivers
postgres
Package postgres provides a PostgreSQL driver for GORM, offering tools to facilitate the construction and management of multi-tenant applications.
Package postgres provides a PostgreSQL driver for GORM, offering tools to facilitate the construction and management of multi-tenant applications.
internal
testutil
Package testutil provides internal testing utilities for the application.
Package testutil provides internal testing utilities for the application.
middleware
echo
Package echo provides a middleware for the Echo framework.
Package echo provides a middleware for the Echo framework.
nethttp
Package nethttp provides a middleware for the net/http package.
Package nethttp provides a middleware for the net/http package.
schema
postgres
Package postgres provides utilities for managing PostgreSQL schemas in a multi-tenant application.
Package postgres provides utilities for managing PostgreSQL schemas in a multi-tenant application.
Package scopes provides a set of predefined GORM scopes for managing multi-tenant applications using the gorm-multitenancy library.
Package scopes provides a set of predefined GORM scopes for managing multi-tenant applications using the gorm-multitenancy library.
Package tenantcontext provides a context key for the tenant.
Package tenantcontext provides a context key for the tenant.

Jump to

Keyboard shortcuts

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