使用 Go 中的 SQL database 是容易的,只需下列这三步:
// 步骤 1:导入主要的 SQL 包
import "database/sql"
// 步骤 2:导入一个驱动包来明确要使用的 SQL 数据库
import _ "github.com/mattn/go-sqlite3"
// 步骤 3:用一个注册好的驱动名称来打开一个数据库
func main() {
// ...
db, err := sql.Open("sqlite3", "database.db")
// ...
}
从这时候开始,对象 db
可以用相同的代码来查询和修改所有支持的 SQL 数据库。如果我们想从 SQLite 转到 PostgreSQL,类似的做法只需要导入另个一数据库的驱动包,以及在调用 sql.Open
[1] 时传入另一个驱动名称。
在这篇博客里,我想简述一些 database/sql
背后的设计模式及架构。
主要的设计模式
database\sql
的架构受一个整体的设计模式制约。我尝试分析它可能是哪个经典的设计模式,然后策略模式看起来比较接近,尽管它不是那么的一致。如果你认为哪个设计模式更符合,请告诉我 [2]。
它看起来像这样:我们有一个想要呈现给用户的通用接口,并有一个针对每个数据库后端的实现。很显然,它听起来很像经典的接口+实现,Go在这方面特别擅长,它对接口的支持很强大。
所以第一个想法会是:创建一些用户会交互的 DB
接口,并且每个数据库后端都实现这些接口。听起来是不是很简单?
当然,但使用这个方法会有一些问题。记住的是 Go 建议接口尽量小,也就是实现较少的方法。这里我们需要较大一点的 DB
接口,而这导致了一些问题:
添加面向用户的能力是很困难的,因为他们可能需要对接口添加额外的方法。这个破坏了所有接口的实现,并且需要许多独立的项目去维护它们的代码。
让所有数据库后端封装相同的方法是困难的,因为如果用户想直接加方法到
DB
接口,是没有一个原生(不能直接修改接口的方法)的地方去添加的。它需要每个后端独立地实现,这是很浪费的,逻辑也是非常复杂的。如果后端想增加可选的能力,对一个单一的接口来说,不为特定的后端采用类型转换,这是具有挑战性的。
因此,一个更好的想法应该像这样:从后端接口分离出面向用户的类型及方法。如图所示: