在使用存储库模式时,如何在Go中处理数据库连接?

huangapple go评论90阅读模式
英文:

How to handle DB connection in Go when using Repository pattern?

问题

假设我有一个非常简单的存储库接口,只从目标数据库中读取:

type UserRepository interface {
    read(ctx context.Context, id WHAT_TYPE_I_SHOULD_USE_HERE) models.User
}

**注意:**请注意,在id参数中,我不知道应该使用什么类型作为id类型,因为在MongoDB中,id是ObjectId,而在基于模式的数据库中,它可能是一个UUID字段。我的主要数据库是MongoDB,如果有帮助的话,但我可能会切换到基于模式的数据库。

现在我有一个MongoDBRepository结构,它附加了read()方法:

type MongoDBRepository struct {
}

func (mo MongoDBRepository) read(ctx context.Context, id primitive.ObjectID) {
    fmt.Printf("read user %s from MongoDB", id)
}

我有一个连接到MongoDB的方法:

func ConnectMongoDB() (*mongo.Client, context.CancelFunc) {
    client, err := mongo.NewClient(options.Client().ApplyURI(configs.MongoURI()))
    if err != nil {
        log.Fatal(err)
    }

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

    err = client.Connect(ctx)

    err = client.Ping(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    log.Print("Successfully connected to MongoDB!")

    return client, cancel
}

现在,由于接口已经实现,我们可以为MySQL/PostgreSQL创建类似的存储库,并分别创建ConnectMySQL/ConnectPostgreSQL方法。

我遇到的问题是在我的主函数中,我应该如何处理连接到当前数据库存储库的问题,以及如何在控制器中使用它来读取或更新文档/记录?

当我将连接传递给控制器方法时,它的类型被设置为*mongo.Client,我如何将其抽象化,以便我的控制器方法获取数据库连接时不与目标数据库类型绑定?

英文:

Let's assume I have a very simple repository interface that only reads from target database:

type UserRepository interface {
	read(ctx context.Context, id WHAT_TYPE_I_SHOULD_USE_HERE) models.User
}

NOTE: note that in id parameter I do not have any idea what to user as id type as id in MongoDB is ObjectId and in schema based DBs it might be a UUID field. My primary database is MongoDB if it helps, but I might switch to schema-based DBs.

Now I have a MongoDBRepository struct that has read() attached to it:

type MongoDBRepository struct {
}

func (mo MongoDBRepository) read(ctx context.Context, id primitive.ObjectID) {
	fmt.Printf("read user %s from MongoDB", id)
}

I have a method that connects to MongoDB:

func ConnectMongoDB() (*mongo.Client, context.CancelFunc) {
	client, err := mongo.NewClient(options.Client().ApplyURI(configs.MongoURI()))
	if err != nil {
		log.Fatal(err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

	err = client.Connect(ctx)

	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal(err)
	}

	log.Print("Successfully connected to MongoDB!")

	return client, cancel
}

Now as interface is implemented we can have a similar repository for MySQL/PostgreSQL and also ConnectMySQL/ConnectPostgreSQL.

The issue I have is in my main function how should I handle connecting to my current database repository and how to use it in my controllers to read or update a document/record?

When I pass my connection to a controller method its type is set to *mongo.Client how can I abstract it away so my controller method that gets DB connection is not bound to target DB type?

答案1

得分: 2

如何处理连接到当前数据库存储库并在控制器中使用它来读取或更新文档/记录?

我一直在查看提供者文档以获取这个问题的提示。

经过快速搜索,我在Github上找到了我需要的内容:

// Client是表示与MongoDB部署的连接池的句柄。它可以安全地供多个goroutine并发使用。

Client是线程安全的,表示的不是单个连接,而是一个连接池。这意味着我可以在多个控制器/存储库实例之间共享单个Client实例,而客户端可以处理这种情况。

首先是存储库。我们需要注入Client:

type MongoDBRepository struct {
   Client mongo.Client
}

根据当前设计,存储库只包含线程安全的成员,因此它本身就是线程安全的。

这是在应用程序启动代码中创建存储库的代码:

repo := &MongoDBRepository{Client: client}

在控制器中,我们将Repository定义为接口类型,但我们将注入MongoDBRepository结构:

// 控制器代码
type UserController struct {
   Repo UserRepository
}

控制器的初始化代码也应该在应用程序启动时发生:

// 应用程序启动
controller := &UserController{Repo: repo}

要处理特定于数据库的类型(id WHAT_TYPE_I_SHOULD_USE_HERE),您需要将它们实现为泛型。这可能会使您的控制器代码变得相当复杂。考虑将该复杂性隐藏在存储库内部,并公开一些简单的类型,如字符串或UUID。

采用这种方法,您可以轻松切换不同的数据库。您只需要更改应用程序初始化(组合根),而无需更改控制器代码。

附注:多数据库支持非常昂贵。我们已经遇到了ID类型的问题。在将来,您应该准备好停止使用任何特定于数据库的功能,仅使用所有数据库都可用的功能。数据库事务是一种伟大的SQL功能,在Mongo中不可用。在完全致力于多个数据库类型之前,权衡利弊。

更新:

  1. 将控制器实现为具有ServeHTTP(w http.ResponseWriter, r *http.Request)方法的结构体。
  2. main.go中按上述描述创建该结构体的实例。
  3. 使用router.Handle而不是router.HandleFunc将该结构体集成到路由器中。

使用结构体,您应该能够更好地控制控制器依赖项的生命周期。

英文:

> how should I handle connecting to my current database repository and
> how to use it in my controllers to read or update a document/record

I am always checking provider documentation to get a hint for that question.

After quick search, I found what I need in Github:

// Client is a handle representing a pool of connections to a MongoDB deployment. It is safe for concurrent use by
// multiple goroutines. 

Client is thread safe and represent not a single connection but a connection pool. That means that I can share a single Client instance among multiple instances of Controllers/Repositories, and the client can handle that.

Starting with Repositories. We need to inject Client:

type MongoDBRepository struct {
   Client mongo.Client
}

With current design, repository only contain thread safe members, so it is inherently thread safe.

This is the code to create a Repository in app start code:

repo := &MongoDBRepository{Client: client}

In controller, we define Repository as interface type, but we will inject MongoDBRepository struct:

// Controller Code
type UserController struct {
   Repo UserRepository
}

Controllers' initiation code also should happen on application star:

// App start 
controller := &UserController{Repo: repo}

To deal with db specific types (id WHAT_TYPE_I_SHOULD_USE_HERE), you need to implement them as Generics. It could make your Controller code a quite complicated. Consider hiding that complexity inside a Repository and exposing something simple like string or uuid.

With that approach, you easily can switch between different DBs. All you need to change is app initialization (Composition Root) without changing controllers code.

P.S. Multiple databases support is quite expensive. We already faced an issue with ID types. In the future, you should be ready to stop using any DB specific features and use only features available across all the databases. DB transactions is one of the great SQL features that is not available in Mongo. Weight pros and cons before you are fully committed to multiple DB types.

Update:

  1. Implement controller as struct with ServeHTTP(w http.ResponseWriter, r *http.Request) method.
  2. Create instance if that struct in main.go as described above.
  3. integrate that struct into router using router.Handle instead of router.HandleFunc

With struct, you should have better control for Controller dependencies lifecycle.

huangapple
  • 本文由 发表于 2022年7月11日 18:31:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/72937159.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定