为了便于模拟,使用外部存储接口时对操作进行抽象会很有帮助。这可以在更改存储后端时提高可移植性。但如果需要在事务内执行多个操作,这样做就比较尴尬,此时进行复合操作或允许通过上下文对象或其他函数参数传递会比较好。

本节将实现一个非常简单的接口来处理MongoDB中的操作。我们会使用一个接口来持久化和检索对象。

实践

建立 storage.go:

package storage

import "context"

type Item struct {
    Name  string
    Price int64
}

// Storage是我们的存储接口 将使用Mongo存储实现它
type Storage interface {
    GetByName(context.Context, string) (*Item, error)
    Put(context.Context, *Item) error
}

建立 mongoconfig.go:

package storage

import mgo "gopkg.in/mgo.v2"

// MongoStorage实现了storage 接口
type MongoStorage struct {
    *mgo.Session
    DB         string
    Collection string
}

// NewMongoStorage 初始化MongoStorage
func NewMongoStorage(connection, db, collection string) (*MongoStorage, error) {
    session, err := mgo.Dial("localhost")
    if err != nil {
        return nil, err
    }
    ms := MongoStorage{
        Session:    session,
        DB:         db,
        Collection: collection,
    }
    return &ms, nil
}

建立 mongointerface.go:

package storage

import (
    "context"

    "gopkg.in/mgo.v2/bson"
)

// GetByName 查询mongodb以获取具有正确名称的item
func (m *MongoStorage) GetByName(ctx context.Context, name string) (*Item, error) {
    c := m.Session.DB(m.DB).C(m.Collection)
    var i Item
    if err := c.Find(bson.M{"name": name}).One(&i); err != nil {
        return nil, err
    }

    return &i, nil
}

// Put 添加一个item到mongo 中
func (m *MongoStorage) Put(ctx context.Context, i *Item) error {
    c := m.Session.DB(m.DB).C(m.Collection)
    return c.Insert(i)
}

建立 exec.go:

package storage

import (
    "context"
    "fmt"
)

// Exec 初始化存储 storage然后使用存储接口执行操作
func Exec() error {
    m, err := NewMongoStorage("localhost", "gocookbook", "items")
    if err != nil {
        return err
    }
    if err := PerformOperations(m); err != nil {
        return err
    }

    if err := m.Session.DB(m.DB).C(m.Collection).DropCollection(); err != nil {
        return err
    }

    return nil
}

// PerformOperations 演示创建并存入一个item然后对其进行查询查询
func PerformOperations(s Storage) error {
    ctx := context.Background()
    i := Item{Name: "candles", Price: 100}
    if err := s.Put(ctx, &i); err != nil {
        return err
    }

    candles, err := s.GetByName(ctx, "candles")
    if err != nil {
        return err
    }
    fmt.Printf("Result: %#v\n", candles)
    return nil
}

建立 main.go:

package main

import "github.com/agtorre/go-cookbook/chapter5/storage"

func main() {
    if err := storage.Exec(); err != nil {
        panic(err)
    }
}

这会输出:

Result: &storage.Item{Name:"candles", Price:100}

说明

在PerformOperation中我们可以看到,此函数将存储接口作为参数。这意味着我们可以动态替换底层存储,而无需修改此函数。如果想要将存储连接到单独的API以便使用和修改它将很简单。

我们为这些接口添加了一个context,以增加额外的灵活性,并允许接口处理超时。将应用程序逻辑与底层存储分离可提供多种好处,但是很难选择正确的位置来处理边界,这在不同的应用程序中会有很大差异。

最后编辑: kuteng  文档更新时间: 2021-01-03 15:03   作者:kuteng