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

How to make database connection switchable at runtime when using SQLC

我知道这个问题已经被问过很多次了,但我没有找到适合我的情况的答案。我正在使用 SQLC 生成查询数据库的方法。使用一开始就初始化的连接时一切正常。现在我需要在多租户环境中设置它,每个租户都有一个单独的数据库。现在我想从连接租户与数据库连接的连接映射 (map[string]*sql.DB) 开始。我的问题是关于 overriding/selecting 运行时的连接。通过一个连接,存储库被初始化为:

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 或上下文)建立连接?

假设您使用单独的数据库,最简单的方法是维护一个 map[tenantID]Repository,其中 tenantID 是您区分租户的方式(例如 stringuint 包含租户 ID)。

这样你就可以在运行时做任何事情:

  • 当您需要添加租户时,只需为该租户实例化 Repository 并将其添加到地图中
  • 当您需要删除租户时,只需从映射中删除其 Repository 并关闭数据库连接
  • 当需要查询某个租户时,在map中查找对应的Repository,并使用它来对该租户进行查询

如果上述操作可能同时发生,请确保您在访问地图时使用某种同步机制来避免数据竞争(例如 sync.Map,或 sync.RWMutex)。

如果你有一个数据库table存储租户和他们的数据库连接URI,你仍然可以使用这种方法:当你需要执行查询检查Repository是否存在于map:如果缺少,查询租户 table 并将该租户的 Repository 添加到地图。然后您可以定期扫描 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)
}

这将避免将多租户设置的所有复杂性暴露给您的业务逻辑。