Like Share Discussion Bookmark Smile

J.J. Huang   2025-06-22   Getting Started Golang 07.套件   瀏覽次數:次   DMCA.com Protection Status

Go | 資料庫操作的抽象套件

💬 簡介

在大型專案中,直接在每個功能裡撰寫 SQL 與資料庫邏輯會造成程式難以維護。
為了實現模組化與抽象化設計,我們可以封裝一層「通用資料庫套件」,讓應用層專注在商業邏輯而非細節實作。

本篇將實作一個抽象的資料庫操作套件,支援 PostgreSQL 與 MySQL,並展示其在應用程式中的使用方式。

圖片來源:Gophers


🧱 套件結構設計

1
2
3
4
5
/db/
|- db.go // 抽象介面定義
|- mysql.go // MySQL 實作
|- postgres.go // PostgreSQL 實作
|- factory.go // 依設定初始化驅動
  • db.go:定義通用的資料存取介面
  • mysql.go / postgres.go:實作不同驅動的邏輯
  • factory.go:統一初始化與切換驅動實作

🔧 抽象介面定義(db/db.go)

1
2
3
4
5
6
7
8
package db

type DB interface {
Connect(dsn string) error
Query(query string, args ...any) ([]map[string]any, error)
Exec(query string, args ...any) (int64, error)
Close() error
}

📝 抽象介面能讓我們對上層隱藏底層驅動差異。

🐬 MySQL 實作(db/mysql.go)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package db

import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)

type mysqlDB struct {
conn *sql.DB
}

func (m *mysqlDB) Connect(dsn string) error {
db, err := sql.Open("mysql", dsn)
if err != nil {
return err
}
m.conn = db
return db.Ping()
}

func (m *mysqlDB) Query(query string, args ...any) ([]map[string]any, error) {
rows, err := m.conn.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()

columns, _ := rows.Columns()
results := []map[string]any{}

for rows.Next() {
values := make([]any, len(columns))
pointers := make([]any, len(columns))
for i := range values {
pointers[i] = &values[i]
}
_ = rows.Scan(pointers...)
row := map[string]any{}
for i, col := range columns {
row[col] = values[i]
}
results = append(results, row)
}
return results, nil
}

func (m *mysqlDB) Exec(query string, args ...any) (int64, error) {
res, err := m.conn.Exec(query, args...)
if err != nil {
return 0, err
}
return res.RowsAffected()
}

func (m *mysqlDB) Close() error {
return m.conn.Close()
}

🐘 PostgreSQL 實作(db/postgres.go)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package db

import (
"database/sql"
_ "github.com/lib/pq"
)

type postgresDB struct {
conn *sql.DB
}

func (p *postgresDB) Connect(dsn string) error {
db, err := sql.Open("postgres", dsn)
if err != nil {
return err
}
p.conn = db
return db.Ping()
}

// Query 與 Exec 實作與 mysqlDB 類似,可共用邏輯或進一步抽象
func (p *postgresDB) Query(query string, args ...any) ([]map[string]any, error) {
// 類似 mysqlDB 實作
// ...
}

func (p *postgresDB) Exec(query string, args ...any) (int64, error) {
// ...
}

func (p *postgresDB) Close() error {
return p.conn.Close()
}

🏭 工廠模式初始化(db/factory.go)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package db

import "errors"

func New(driver string) (DB, error) {
switch driver {
case "mysql":
return &mysqlDB{}, nil
case "postgres":
return &postgresDB{}, nil
default:
return nil, errors.New("unsupported driver: " + driver)
}
}

🚀 main.go:實際使用套件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"myapp/db"
)

func main() {
dbDriver := "mysql"
dsn := "user:pass@tcp(127.0.0.1:3306)/mydb"

client, err := db.New(dbDriver)
if err != nil {
panic(err)
}
defer client.Close()

err = client.Connect(dsn)
if err != nil {
panic(err)
}

result, _ := client.Query("SELECT id, name FROM users WHERE active = ?", true)
for _, row := range result {
fmt.Println(row["id"], row["name"])
}
}

📌 擴充性與應用建議

應用方向 說明
支援更多驅動 新增 sqlite.gomssql.go 等實作
共用資料格式轉換工具 封裝常見資料格式轉換邏輯
統一錯誤回傳格式 包裝回傳錯誤類型,方便除錯
加入 ORM 介接層 可與 GORM 等工具整合

🎯 總結

透過抽象介面與工廠模式,我們可以輕鬆替換資料庫實作而不影響上層邏輯,實現:

  • ✅ 支援多種資料庫驅動
  • ✅ 抽象資料操作流程
  • ✅ 提升可維護性與可測試性
  • ✅ 更容易撰寫單元測試與模擬(mock)

這就是實務開發中,利用套件封裝「程式碼解耦」的最佳案例之一!

最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。


註:以上參考了
Go