saas

package module
v0.6.4 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2024 License: MIT Imports: 7 Imported by: 25

README

jace996

English | 中文文档

headless go framework for saas(multi-tenancy).
jace996 targets to provide saas solution for go this project suits for simple (web) project, which is also called monolithic.

if you are finding complete solution which is microservice compatible, please refer to jace996-kit

Overview

Feature

  • Different database architecture

    • Single-tenancy: Each database stores data from only one tenant.

    img.png

    • Multi-tenancy: Each database stores data from multiple separate tenants (with mechanisms to protect data privacy).

    img.png

    • Hybrid tenancy models are also available.

    • Implement your own resolver to achieve style like sharding

  • Support multiple web framework

  • Supported orm with data filter, which means all underlying database

  • Customizable tenant resolver

    • Query String
    • Form parameters
    • Header
    • Cookie
    • Domain format
  • Seed and Migration

    • Seed/Migrate tenant database after creation or upgrade to new version
  • Integration with gateway

Install

go get github.com/jace996/saas

Design

graph TD
    A(InComming Request) -->|cookie,domain,form,header,query...|B(TenantResolver)
    B --> C(Tenant Context)  --> D(ConnectionString Resolver)
    D --> E(Tenant 1) --> J(Data Filter) -->  H(Shared Database)
    D --> F(Tenant 2) --> J
    D --> G(Tenant 3) --> I(Tenant 3 Database)

Sample Project

  • example-gorm combination of jace996,gin,gorm(sqlite/mysql)
  • example-ent combination of jace996,gin,ent(sqlite)
  • jace996-kit Microservice architecture starter kit for golang sass project

Documentation

Refer to wiki

References

https://docs.microsoft.com/en-us/azure/azure-sql/database/saas-tenancy-app-design-patterns

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTenantNotFound = errors.New("tenant not found")
)

Functions

func NewCurrentTenant

func NewCurrentTenant(ctx context.Context, id, name string) context.Context

func NewCurrentTenantInfo

func NewCurrentTenantInfo(ctx context.Context, info TenantInfo) context.Context

func NewTenantConfigContext

func NewTenantConfigContext(ctx context.Context, tenantId string, cfg *TenantConfig) context.Context

func NewTenantResolveRes

func NewTenantResolveRes(ctx context.Context, t *TenantResolveResult) context.Context

func WithDatabaseStyle

func WithDatabaseStyle(databaseStyle DatabaseStyleType) option

WithDatabaseStyle database style, support Single/PerTenant/Multi

func WithEnabled

func WithEnabled(isEnabled bool) option

WithEnabled enable status

Types

type BasicTenantInfo

type BasicTenantInfo struct {
	Id   string
	Name string
}

func NewBasicTenantInfo

func NewBasicTenantInfo(id string, name string) *BasicTenantInfo

func (*BasicTenantInfo) GetId

func (b *BasicTenantInfo) GetId() string

func (*BasicTenantInfo) GetName

func (b *BasicTenantInfo) GetName() string

type Cache

type Cache[K comparable, V io.Closer] struct {
	// contains filtered or unexported fields
}

Cache is used a LRU (Least recently used) cache replacement policy. adapted from https://github.com/Code-Hex/go-generics-cache/blob/main/policy/lru/lru.go

Discards the least recently used items first. This algorithm requires keeping track of what was used when, which is expensive if one wants to make sure the algorithm always discards the least recently used item.

func NewCache

func NewCache[K comparable, V io.Closer](opts ...Option) *Cache[K, V]

NewCache creates a new thread safe LRU cache whose capacity is the default size (128).

func (*Cache[K, V]) Delete

func (c *Cache[K, V]) Delete(key K)

Delete deletes the item with provided key from the cache.

func (*Cache[K, V]) Flush

func (c *Cache[K, V]) Flush() error

Flush delete all items

func (*Cache[K, V]) Get

func (c *Cache[K, V]) Get(key K) (zero V, _ bool)

Get looks up a key's value from the cache.

func (*Cache[K, V]) GetOrSet

func (c *Cache[K, V]) GetOrSet(key K, factory func() (V, error)) (zero V, set bool, err error)

GetOrSet combine Get and Set

func (*Cache[K, V]) Keys

func (c *Cache[K, V]) Keys() []K

Keys returns the keys of the cache. the order is from oldest to newest.

func (*Cache[K, V]) Len

func (c *Cache[K, V]) Len() int

Len returns the number of items in the cache.

func (*Cache[K, V]) Set

func (c *Cache[K, V]) Set(key K, val V)

type ClientProvider

type ClientProvider[TClient interface{}] interface {
	Get(ctx context.Context, dsn string) (TClient, error)
}

ClientProvider resolve by dsn string (connection string)

type ClientProviderFunc

type ClientProviderFunc[TClient interface{}] func(ctx context.Context, dsn string) (TClient, error)

ClientProviderFunc see ClientProvider

func (ClientProviderFunc[TClient]) Get

func (c ClientProviderFunc[TClient]) Get(ctx context.Context, dsn string) (TClient, error)

type ConnStrGenerator

type ConnStrGenerator interface {
	Gen(ctx context.Context, tenant TenantInfo) (string, error)
}

ConnStrGenerator generate connection string for tenant. useful for tenant creation

type Context

type Context struct {
	TenantIdOrName string
	// HasHandled field to handle host side unresolved or resolved
	HasHandled bool
	// contains filtered or unexported fields
}

func NewTenantResolveContext

func NewTenantResolveContext(ctx context.Context) *Context

func (*Context) Context

func (t *Context) Context() context.Context

func (*Context) HasResolved

func (t *Context) HasResolved() bool

func (*Context) WithContext

func (t *Context) WithContext(ctx context.Context)

type ContextContrib

type ContextContrib struct {
}

ContextContrib resolve from current context

func (*ContextContrib) Name

func (c *ContextContrib) Name() string

func (*ContextContrib) Resolve

func (c *ContextContrib) Resolve(ctx *Context) error

type DatabaseStyleType

type DatabaseStyleType int32
const (
	Single    DatabaseStyleType = 1 << 0
	PerTenant DatabaseStyleType = 1 << 1
	Multi     DatabaseStyleType = 1 << 2
)

type DbProvider

type DbProvider[TClient interface{}] interface {
	// Get instance by key
	Get(ctx context.Context, key string) TClient
}

DbProvider resolve TClient from user friendly key

type DefaultConnStrGenerator

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

func NewConnStrGenerator

func NewConnStrGenerator(format string) *DefaultConnStrGenerator

func (*DefaultConnStrGenerator) Gen

type DefaultDbProvider

type DefaultDbProvider[TClient interface{}] struct {
	// contains filtered or unexported fields
}

DefaultDbProvider resolve dsn from user friendly key by data.ConnStrResolver, then resolve TClient from dsn by ClientProvider

func NewDbProvider

func NewDbProvider[TClient interface{}](cs data.ConnStrResolver, cp ClientProvider[TClient]) (d *DefaultDbProvider[TClient])

func (*DefaultDbProvider[TClient]) Get

func (d *DefaultDbProvider[TClient]) Get(ctx context.Context, key string) TClient

type DefaultTenantConfigProvider

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

func (*DefaultTenantConfigProvider) Get

Get read from context FromTenantConfigContext first, fallback with TenantStore and return new context with cached value

type DefaultTenantResolver

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

func (*DefaultTenantResolver) Resolve

type MemoryTenantStore

type MemoryTenantStore struct {
	TenantConfig []TenantConfig
}

func NewMemoryTenantStore

func NewMemoryTenantStore(t []TenantConfig) *MemoryTenantStore

func (*MemoryTenantStore) GetByNameOrId

func (m *MemoryTenantStore) GetByNameOrId(_ context.Context, nameOrId string) (*TenantConfig, error)

type MultiTenancyConnStrResolver

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

func NewMultiTenancyConnStrResolver

func NewMultiTenancyConnStrResolver(ts TenantStore, fallback data.ConnStrResolver) *MultiTenancyConnStrResolver

NewMultiTenancyConnStrResolver from tenant

func (*MultiTenancyConnStrResolver) Resolve

type MultiTenancyOption

type MultiTenancyOption struct {
	IsEnabled     bool
	DatabaseStyle DatabaseStyleType
}

func DefaultMultiTenancyOption

func DefaultMultiTenancyOption() *MultiTenancyOption

func NewMultiTenancyOption

func NewMultiTenancyOption(opts ...option) *MultiTenancyOption

type MultiTenancySide

type MultiTenancySide int32
const (
	Tenant MultiTenancySide = 1 << 0
	Host   MultiTenancySide = 1 << 1
	Both                    = Tenant | Host
)

func GetMultiTenantSide

func GetMultiTenantSide(ctx context.Context) MultiTenancySide

type Option

type Option func(*options)

Option is an option for LRU cache.

func WithCapacity

func WithCapacity(cap int) Option

WithCapacity is an option to set cache capacity.

type ResolveOption

type ResolveOption func(resolveOption *TenantResolveOption)

func AppendContribs

func AppendContribs(c ...TenantResolveContrib) ResolveOption

func RemoveContribs

func RemoveContribs(c ...TenantResolveContrib) ResolveOption

type TenantConfig

type TenantConfig struct {
	ID      string           `json:"id"`
	Name    string           `json:"name"`
	Region  string           `json:"region"`
	PlanKey string           `json:"planKey"`
	Conn    data.ConnStrings `json:"conn"`
}

func FromTenantConfigContext

func FromTenantConfigContext(ctx context.Context, tenantId string) (*TenantConfig, bool)

func NewTenantConfig

func NewTenantConfig(id, name, region, planKey string) *TenantConfig

type TenantConfigProvider

type TenantConfigProvider interface {
	// Get tenant config
	Get(ctx context.Context) (TenantConfig, context.Context, error)
}

TenantConfigProvider resolve tenant config from current context

func NewDefaultTenantConfigProvider

func NewDefaultTenantConfigProvider(tr TenantResolver, ts TenantStore) TenantConfigProvider

type TenantInfo

type TenantInfo interface {
	GetId() string
	GetName() string
}

func FromCurrentTenant

func FromCurrentTenant(ctx context.Context) (TenantInfo, bool)

type TenantNormalizerContrib

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

TenantNormalizerContrib normalize tenant id or name into tenant id

func NewTenantNormalizerContrib

func NewTenantNormalizerContrib(ts TenantStore) *TenantNormalizerContrib

func (*TenantNormalizerContrib) Name

func (t *TenantNormalizerContrib) Name() string

func (*TenantNormalizerContrib) Resolve

func (t *TenantNormalizerContrib) Resolve(ctx *Context) error

type TenantResolveContrib

type TenantResolveContrib interface {
	// Name of resolver
	Name() string
	// Resolve tenant
	Resolve(ctx *Context) error
}

type TenantResolveOption

type TenantResolveOption struct {
	Resolvers []TenantResolveContrib
}

func NewTenantResolveOption

func NewTenantResolveOption(c ...TenantResolveContrib) *TenantResolveOption

func (*TenantResolveOption) AppendContribs

func (opt *TenantResolveOption) AppendContribs(c ...TenantResolveContrib)

func (*TenantResolveOption) RemoveContribs

func (opt *TenantResolveOption) RemoveContribs(c ...TenantResolveContrib)

type TenantResolveResult

type TenantResolveResult struct {
	TenantIdOrName   string
	AppliedResolvers []string
}

func FromTenantResolveRes

func FromTenantResolveRes(ctx context.Context) *TenantResolveResult

type TenantResolver

type TenantResolver interface {
	Resolve(ctx context.Context) (TenantResolveResult, context.Context, error)
}

func NewDefaultTenantResolver

func NewDefaultTenantResolver(opt ...ResolveOption) TenantResolver

type TenantStore

type TenantStore interface {
	// GetByNameOrId return nil and ErrTenantNotFound if tenant not found
	GetByNameOrId(ctx context.Context, nameOrId string) (*TenantConfig, error)
}

Directories

Path Synopsis
examples
ent Module
gorm Module
gateway

Jump to

Keyboard shortcuts

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