I know this question is asked already many times, but I did not find a good answer for my case. I'm using SQLC to generate methods for querying the db. Everything is working fine when using one connection initialised at start. Now I need to set it up in a multi-tenant environment where each tenant will have a separate DB. For now I would like to start with a connection map (map[string]*sql.DB) connecting the tenant with a database connection. My question is about overriding/selecting the connection at runtime. with one connection the repository is initialised like:
type Repository interface {
GetCustomerById(ctx context.Context, id int64) (Customer, error)
ListCustomers(ctx context.Context) ([]Customer, error)
}
type repoSvc struct {
*Queries
db *sql.DB
}
func NewRepository(dbconn *sql.DB) Repository {
return &repoSvc{
Queries: New(dbconn),
db: dbconn,
}
}
customerRepo := customerRepo.NewRepository(conn)
GetCustomerById is the SQLC generated method conn is the database connection
How to make the connection based on a parameter (from cookie or context)?
The simplest way, assuming you are using separate databases, is to maintain a map[tenantID]Repository
, where tenantID
is the way you differentiate between tenants (e.g. a string
or uint
that contains the tenant ID).
This way you can do everything at runtime:
Repository
for that tenant and add it to the mapRepository
from the map and close the DB connectionRepository
in the map, and use it to perform the query for that tenantIf the operations above may happen concurrently, make sure that you're using some synchronization mechanism to avoid data races when accessing the map (e.g. sync.Map
, or sync.RWMutex
).
If you have a database table that stores the tenants and their DB connection URIs, you can still use this approach: when you need to perform a query check if the Repository
exists in the map
: if it's missing, query the tenant table and add the Repository
for that tenant to the map. You can then periodically scan the map
and remove any Repository
that has not been used for some time.
To make all of this easier you could also wrap the whole machinery into a MultitenantRepository
interface, that is identical to the Repository
interface but that accepts an additional tenantID
parameter on each method:
type MultitenantRepository interface {
GetCustomerById(ctx context.Context, tenant tenantID, id int64) (Customer, error)
ListCustomers(ctx context.Context, tenant tenantID) ([]Customer, error)
}
This will avoid exposing all the complexity of your multitenant setup to your business logic.