在使用SQLC时,如何在运行时使数据库连接可切换?

huangapple go评论112阅读模式
英文:

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的stringuint)。

这样你可以在运行时完成以下操作:

  • 当你需要添加一个租户时,只需实例化该租户的Repository并将其添加到map中
  • 当你需要删除一个租户时,只需从map中删除其Repository并关闭数据库连接
  • 当你需要为一个租户执行查询时,在map中查找相应的Repository,并使用它来执行该租户的查询

如果上述操作可能同时发生,请确保在访问map时使用某种同步机制以避免数据竞争(例如sync.Mapsync.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.

huangapple
  • 本文由 发表于 2021年8月5日 02:08:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/68656132.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定