Golang中实现可动态切换的多数据库仓库方案问询
问题说明
当前运行的Go API通过仓库层从MySQL数据库获取数据,需要实现相同的仓库逻辑以从MongoDB获取数据,同时要求能根据API配置动态切换数据库(MySQL/MongoDB)。现有代码仅支持单一数据库模式,尝试过三种方案后,希望让「不同仓库实现」的方案可行,避免大量修改现有调用代码。
现有代码
// My vehicle repository file // Repository handling Vehicle type VehicleRepository struct { conn *sqlx.DB } func (r *VehicleRepository) GetVehicles(ctx context.Context, brand string) ([]*vehicle.Vehicle, error) { var vehicles []*vehicle.Vehicle param := map[string]interface{}{"brand": brand} query := "SELECT * FROM vehicles WHERE brand = :brand" rows, err := r.conn.NamedQueryContext(ctx, query, param) if err != nil { return nil, errors.Wrap(err, "query execution") } return vehicles, nil } // Repository集合定义(实际包含30+仓库) type Repositories struct { Vehicle *VehicleRepository } func CreateRepositories(conn c.Connections) *Repositories { return &Repositories{ Vehicle: &VehicleRepository{conn, l}, } }
已尝试的方案
- 方案1:拆分仓库实现,将原
vehicle.go改为vehicle.mysql.go并创建vehicle.mongodb.go,分别实现VehicleMySQLRepository和VehicleMongoDBRepository,但无法在Repositories结构体中统一类型,且修改50+调用文件成本过高。 - 方案2:条件编译,通过
go build -tags mongo编译对应版本,但无法动态切换,不符合前端用户可选择数据库的需求。 - 方案3:同一仓库兼容双数据库,在
GetVehicles等方法中加入条件判断处理两种数据库逻辑,虽可行但代码臃肿。
解决方案:基于接口统一仓库类型
核心思路是定义仓库接口,让MySQL和MongoDB的仓库实现都满足该接口,然后在Repositories中使用接口类型而非具体结构体,这样调用代码无需修改,仅需在创建仓库时根据配置动态选择实例。
步骤1:定义仓库接口
在vehicle包或单独的仓库接口文件中定义接口:
// vehicle/repository.go package vehicle import "context" // VehicleRepository 定义车辆仓库的核心方法 type VehicleRepository interface { GetVehicles(ctx context.Context, brand string) ([]*Vehicle, error) // 其他需要的方法(如CreateVehicle、UpdateVehicle等) }
步骤2:实现MySQL和MongoDB仓库
分别实现接口,保持原有逻辑不变:
// vehicle/repository_mysql.go package vehicle import ( "context" "errors" "github.com/jmoiron/sqlx" ) type VehicleMySQLRepository struct { conn *sqlx.DB } // 编译期检查是否实现接口 var _ VehicleRepository = (*VehicleMySQLRepository)(nil) func NewVehicleMySQLRepository(conn *sqlx.DB) *VehicleMySQLRepository { return &VehicleMySQLRepository{conn: conn} } func (r *VehicleMySQLRepository) GetVehicles(ctx context.Context, brand string) ([]*Vehicle, error) { var vehicles []*Vehicle param := map[string]interface{}{"brand": brand} query := "SELECT * FROM vehicles WHERE brand = :brand" rows, err := r.conn.NamedQueryContext(ctx, query, param) if err != nil { return nil, errors.Wrap(err, "mysql query execution") } defer rows.Close() // 补充原代码缺失的行扫描逻辑 for rows.Next() { var v Vehicle if err := rows.StructScan(&v); err != nil { return nil, errors.Wrap(err, "scan vehicle row") } vehicles = append(vehicles, &v) } if err := rows.Err(); err != nil { return nil, errors.Wrap(err, "rows iteration error") } return vehicles, nil }
// vehicle/repository_mongodb.go package vehicle import ( "context" "errors" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) type VehicleMongoDBRepository struct { coll *mongo.Collection } // 编译期检查是否实现接口 var _ VehicleRepository = (*VehicleMongoDBRepository)(nil) func NewVehicleMongoDBRepository(coll *mongo.Collection) *VehicleMongoDBRepository { return &VehicleMongoDBRepository{coll: coll} } func (r *VehicleMongoDBRepository) GetVehicles(ctx context.Context, brand string) ([]*Vehicle, error) { var vehicles []*Vehicle filter := bson.M{"brand": brand} cursor, err := r.coll.Find(ctx, filter) if err != nil { return nil, errors.Wrap(err, "mongodb find execution") } defer cursor.Close(ctx) if err := cursor.All(ctx, &vehicles); err != nil { return nil, errors.Wrap(err, "decode mongodb documents") } return vehicles, nil }
步骤3:修改Repositories结构体和创建函数
将具体结构体替换为接口类型,并根据配置动态实例化对应仓库:
// repositories.go type Repositories struct { Vehicle vehicle.VehicleRepository // 使用接口类型 // 其他仓库也替换为对应的接口 } // 假设配置包含数据库类型标识 type Config struct { DatabaseType string // 可选值:"mysql" / "mongodb" // 其他配置项(如MySQL连接、MongoDB连接) } func CreateRepositories(conn c.Connections, cfg Config) *Repositories { repos := &Repositories{} switch cfg.DatabaseType { case "mysql": repos.Vehicle = vehicle.NewVehicleMySQLRepository(conn.MySQL) case "mongodb": repos.Vehicle = vehicle.NewVehicleMongoDBRepository(conn.MongoDB.VehiclesColl) default: panic("unsupported database type") } // 初始化其他仓库... return repos }
步骤4:调用代码无需修改
原有调用repo.Vehicle.GetVehicles(ctx, brand)的代码完全不需要改动,因为接口方法签名与原结构体方法一致。
优势说明
- 完全兼容现有调用代码,无需修改50+文件;
- 符合开闭原则,新增数据库类型只需实现对应接口,不修改现有逻辑;
- 支持动态切换,只需在创建仓库时根据配置选择实例;
- 编译期检查接口实现,避免运行时错误。
内容的提问来源于stack exchange,提问作者Paul-Marie
相关产品推荐
相关产品推荐

