应使用注册表+工厂函数管理多数据库适配器,而非硬编码if-else;因后者随新数据源接入迅速腐化,维护难、测试难、部署风险高,且反射取类型名不可靠、无法配置、缺乏编译期校验。

直接上结论:用注册表 + 工厂函数管理不同数据库后端的适配器实例,而不是写一堆 if-else 判断类型再 new;每个适配器用 struct 包装原客户端,只实现目标接口缺失或签名不一致的方法,别全量手写。
为什么不能用 if-else 做多数据库适配
硬编码判断配置名再构造对应适配器,会随新数据库接入(比如从 "postgres" 扩展到 "clickhouse"、"redis-json")迅速腐化。每次加新源都要改主逻辑,测试覆盖难,部署风险高。
-
if config.Driver == "mysql" { return &MySQLAdapter{client: newMySQL()} }—— 这类分支在main或初始化函数里出现三次以上,就该重构了 - 反射取类型名(如
reflect.TypeOf(db).Name())不可靠:类型名易变、无法配置、编译期无法校验键存在性 - 工厂函数返回接口(如
datastore.Interface),内部隐藏具体适配器实现,调用方完全不感知底层是 MySQL 还是 SQLite
注册表怎么建才安全可靠
注册表本质是 map[string]func() datastore.Interface,但必须保证运行时只读、初始化阶段完成注册,否则并发访问会 panic。
- 全局变量定义:
var adapters = make(map[string]func() datastore.Interface) - 注册动作放在
init()函数或显式RegisterAdapters()里,例如:adapters["postgres"] = func() datastore.Interface { return &PostgresAdapter{conn: pgConn} } - 使用时查表:
factory := adapters[config.Driver]; if factory == nil { panic("unknown driver: " + config.Driver) } - 键名用配置项值(
"postgres"、"sqlite3"),不是类型名或包路径——这样运维可通过 YAML 切换后端,无需改代码
适配器 struct 里该嵌入还是显式字段
取决于你是否需要控制方法暴露粒度。多数数据库客户端(如 *sql.DB、*pgxpool.Pool)方法太多,直接嵌入会把无关方法(如 Stats()、Close())意外提升到目标接口上,破坏封装。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 推荐显式字段:
type PostgresAdapter struct { db *pgxpool.Pool },然后手动实现Get()、Put()等目标接口方法 - 仅当被适配对象方法极少且全部需透传时才考虑嵌入:
type LegacyDBAdapter struct { *LegacyDB },但要检查是否有命名冲突(比如两个接口都有Close()但语义不同) - 所有适配器构造函数必须校验依赖非 nil:
if db == nil { panic("nil db client") },否则运行时调用a.db.QueryRow()直接 crash
最容易被忽略的三个 runtime 坑
适配器不是胶水层,而是协议转换点。这三个地方不处理,上线后第一波流量就 panic:
-
context.Context丢失:老客户端(如旧版database/sql封装)不接收ctx,但目标接口要求Get(ctx, key string) (any, error)—— 必须在适配器里显式降级:return a.db.QueryRow("SELECT ...", key).Scan(&v),别裸调用无 ctx 版本 - 错误包装不一致:原客户端返回
error,目标接口要求返回(any, error),但业务方靠errors.Is(err, datastore.ErrNotFound)判断 —— 必须用fmt.Errorf("wrap: %w", err)包装,不能直接return nil, err - 空指针未防护:适配器字段(如
db)为 nil 时,方法内直接调用a.db.QueryRow会 panic —— 构造函数里强制校验,别指望调用方传进来一定合法
复杂点不在“怎么写”,而在“谁负责上下文超时、重试、熔断”——这些逻辑必须剥离出适配器,放到中间件或调用方,否则适配器会变成状态黑洞,难以测试和替换。

















