英文:
How to make database connection switchable at runtime when using SQLC
问题
我知道这个问题已经被问了很多次,但是我没有找到一个适合我情况的好答案。我正在使用SQLC来生成查询数据库的方法。当我在开始时使用一个连接初始化时,一切都正常工作。现在我需要在一个多租户环境中设置它,每个租户将有一个单独的数据库。现在我想从一个连接映射(map[string]*sql.DB)开始,将租户与数据库连接起来。我的问题是如何在运行时覆盖/选择连接。使用一个连接时,存储库的初始化如下:
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是由SQLC生成的方法
conn是数据库连接
如何根据参数(来自cookie或上下文)建立连接?
英文:
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)?
答案1
得分: 0
最简单的方法是假设你正在使用单独的数据库,可以维护一个map[tenantID]Repository
,其中tenantID
是区分租户的方式(例如包含租户ID的string
或uint
)。
这样你可以在运行时完成以下操作:
- 当你需要添加一个租户时,只需实例化该租户的
Repository
并将其添加到map中 - 当你需要删除一个租户时,只需从map中删除其
Repository
并关闭数据库连接 - 当你需要为一个租户执行查询时,在map中查找相应的
Repository
,并使用它来执行该租户的查询
如果上述操作可能同时发生,请确保在访问map时使用某种同步机制以避免数据竞争(例如sync.Map
或sync.RWMutex
)。
如果你有一个存储租户及其数据库连接URI的数据库表,你仍然可以使用这种方法:当你需要执行查询时,检查map
中是否存在Repository
:如果缺失,则查询租户表并将该租户的Repository
添加到map中。然后,定期扫描map
并删除一段时间内未被使用的任何Repository
。
为了使所有这些更容易,你还可以将整个机制封装到一个MultitenantRepository
接口中,该接口与Repository
接口完全相同,但在每个方法上接受一个额外的tenantID
参数:
type MultitenantRepository interface {
GetCustomerById(ctx context.Context, tenant tenantID, id int64) (Customer, error)
ListCustomers(ctx context.Context, tenant tenantID) ([]Customer, error)
}
这将避免将多租户设置的所有复杂性暴露给业务逻辑。
英文:
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:
- when you need to add a tenant, just instantiate the
Repository
for that tenant and add it to the map - when you need to remove a tenant, just remove its
Repository
from the map and close the DB connection - when you need to perform a query for a tenant, lookup the corresponding
Repository
in the map, and use it to perform the query for that tenant
If 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论