1. 设计通用接口
// DataStore 定义数据存储接口
type DataStore interface {
Read(key string) (interface{}, error)
Write(key string, value interface{}) error
}
2. 针对每种存储方式实现接口
MySQL 实现
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
)
type MySQLStore struct {
db *sql.DB
}
func NewMySQLStore(dsn string) (*MySQLStore, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return &MySQLStore{db: db}, nil
}
func (m *MySQLStore) Read(key string) (interface{}, error) {
var result interface{}
query := "SELECT value FROM your_table WHERE key =?"
err := m.db.QueryRow(query, key).Scan(&result)
if err != nil {
return nil, err
}
return result, nil
}
func (m *MySQLStore) Write(key string, value interface{}) error {
query := "INSERT INTO your_table (key, value) VALUES (?,?)"
_, err := m.db.Exec(query, key, value)
return err
}
Redis 实现
package main
import (
"github.com/go-redis/redis/v8"
"context"
"fmt"
)
type RedisStore struct {
client *redis.Client
ctx context.Context
}
func NewRedisStore(addr string, password string, db int) (*RedisStore, error) {
client := redis.NewClient(&redis.Options{
Addr: addr,
Password: password,
DB: db,
})
ctx := context.Background()
_, err := client.Ping(ctx).Result()
if err != nil {
return nil, err
}
return &RedisStore{client: client, ctx: ctx}, nil
}
func (r *RedisStore) Read(key string) (interface{}, error) {
result, err := r.client.Get(r.ctx, key).Result()
if err != nil {
return nil, err
}
return result, nil
}
func (r *RedisStore) Write(key string, value interface{}) error {
err := r.client.Set(r.ctx, key, value, 0).Err()
return err
}
MongoDB 实现
package main
import (
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"fmt"
)
type MongoDBStore struct {
collection *mongo.Collection
ctx context.Context
}
func NewMongoDBStore(uri string, dbName string, collectionName string) (*MongoDBStore, error) {
ctx := context.Background()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
if err != nil {
return nil, err
}
collection := client.Database(dbName).Collection(collectionName)
return &MongoDBStore{collection: collection, ctx: ctx}, nil
}
func (m *MongoDBStore) Read(key string) (interface{}, error) {
var result bson.M
filter := bson.M{"key": key}
err := m.collection.FindOne(m.ctx, filter).Decode(&result)
if err != nil {
return nil, err
}
return result["value"], nil
}
func (m *MongoDBStore) Write(key string, value interface{}) error {
document := bson.M{
"key": key,
"value": value,
}
_, err := m.collection.InsertOne(m.ctx, document)
return err
}
3. 通过接口复用业务逻辑示例
func ProcessData(store DataStore, key string, value interface{}) error {
err := store.Write(key, value)
if err != nil {
return err
}
result, err := store.Read(key)
if err != nil {
return err
}
fmt.Printf("Read data: %v\n", result)
return nil
}
4. 这样做的好处
- 可维护性:每种存储方式的实现独立,修改一种存储方式的实现不会影响其他存储方式,也不会影响使用该接口的业务逻辑。例如如果要升级MySQL驱动版本,只需要修改MySQLStore的实现,而不会影响到使用RedisStore或MongoDBStore的部分。
- 可扩展性:当需要添加新的数据存储方式时,只需要实现DataStore接口,就可以无缝集成到现有系统中。例如如果后续项目需要引入Elasticsearch存储,只需要按照接口规范实现Read和Write方法即可。
- 代码复用:业务逻辑可以通过接口进行复用,避免在不同存储方式下重复编写相同的读写业务逻辑。例如上述的ProcessData函数,可以处理任何实现了DataStore接口的存储方式。
5. 可能遇到的问题
- 性能差异:不同存储方式性能特性不同,在通用接口抽象下,可能难以针对每种存储进行极致的性能优化。例如Redis适合高速读写缓存数据,而MySQL适合复杂关系型数据处理,在通用接口下可能无法完全发挥各自的性能优势。
- 接口兼容性:随着业务发展,接口可能需要扩展方法,但这可能导致所有实现类都需要修改。例如如果要在接口中添加一个Delete方法,那么MySQLStore、RedisStore、MongoDBStore都需要实现该方法。
- 数据一致性:不同存储方式对数据一致性保证不同,在使用通用接口进行读写操作时,可能导致数据一致性问题。例如Redis的读写操作通常是原子性的,但MySQL在某些复杂事务场景下需要额外处理才能保证一致性。