go-kratos / kratos

Your ultimate Go microservices framework for the cloud-native era.
https://go-kratos.dev
MIT License
23.25k stars 4k forks source link

How to configure when dealing with multiple DB instances? Are there any practices?[Question] #3152

Closed JackChanw closed 6 months ago

JackChanw commented 9 months ago

如果要对多个DB进行操作的话,如何比较方便的添加进去,是通过不同的配置,还是实现一个配置列表,如果是配置列表,在使用上怎么用呢,DB.Use("xxx") 怎么兼容?

kratos-ci-bot commented 9 months ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


If you want to operate multiple DBs, how can you add them more conveniently, through different configurations, or by implementing a configuration list? If it is a configuration list, how to use it, how about DB.Use("xxx") compatible?

dosubot[bot] commented 9 months ago

🤖

你好,@JackChanw!我是Dosu,一个能帮你解决问题,回答问题,甚至成为贡献者的机器人。虽然我是一个机器人,但我还是会尽我所能来帮助你,同时等待人类维护者的回答。我正在查看你的问题,并将在不久的将来给你一个完整的答案。🐸

kratos-ci-bot commented 9 months ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


🤖

Hello @JackChanw! I'm Dosu, a bot that can help you solve problems, answer questions, and even become a contributor. Even though I'm a robot, I'll try my best to help you while waiting for answers from the human maintainers. I'm looking at your question and will give you a complete answer in the near future. 🐸

flc1125 commented 9 months ago

给你个参考,我是这么做的:

  1. 我在 internal/data 目录下创建了个 database.go 文件,

  2. 假设我接入的是 gorm,我会定义一个结构体,大致如下:

    代码做了适当删减,可能有误,但大意是这个意思

    package data
    
    import (
        "time"
    
        "gorm.io/gorm"
        "gorm.io/gorm/logger"
    
        "example/internal/conf"
    )
    
    type DB struct {
        *gorm.DB
    
        One *gorm.DB
        Two *gorm.DB
    }
    
    func NewDB() (*DB, error) {
        def, err := gorm.New(....)
        if err != nil {
            return nil, err
        }
    
        one, err := gorm.New(...)
        two, err := gorm.New(...)
    
        return &DB{
            DB: def,
            One: one,
            Two: two,
        }
    }
  3. 其他地方用的话,就直接可以引入这个 DB 结构体了.

    
    db.Where(...)
    db.One.Where(...)
    db.Two.Where(...)
kratos-ci-bot commented 9 months ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


To give you a reference, this is what I did:

  1. I created a database.go file in the internal/data directory.

  2. Suppose I am connected to gorm, I will define a structure, roughly as follows:

    The code has been appropriately shortened. There may be errors, but the general idea is this.

    
    package data
    
    import (
    "time"

"gorm.io/gorm" "gorm.io/gorm/logger"

"example/internal/conf" )

type DB struct {

*gorm.DB

One gorm.DB Two gorm.DB }

func NewDB() (*DB, error) {

def, err := gorm.New(....) if err != nil { return nil, err }

one, err := gorm.New(...) two, err := gorm.New(...)

return &DB{ DB:def, One: one, Two: two, } }


1. If used elsewhere, you can directly introduce this `DB` structure.

    ```go

    db.Where(...)
    db.One.Where(...)
    db.Two.Where(...)
JellyTony commented 7 months ago

https://github.com/nextmicro/next-component/blob/main/gorm/option.go

//{
//    "mysql": {
//        "default": {
//            "master": 'user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local',
//            "slaves": [
//                'user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local',
//                'user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local'
//            ]
//        }
//    }
//}

func (c *Component) Instance(name ...string) *gorm.DB {
    group := defaultName
    if len(name) > 0 && name[0] != "" {
        group = name[0]
    }

    value, ok := c.clients.Load(group)
    if !ok {
        panic("not found instance" + group)
    }

    return value.(*gorm.DB)
}
dosubot[bot] commented 7 months ago

Based on the information provided, your approach to handling multiple database instances seems to be on the right track. You've encapsulated the database instances within a struct (DB) and are using methods to interact with each instance. This is a good practice as it promotes encapsulation and makes your code more organized and maintainable.

However, there are a few things you might want to consider:

  1. Error Handling: In your NewDB() function, you're creating three instances of gorm.DB (default, one, and two). However, you're not handling errors that might occur when creating the second and third instances. If an error occurs when creating one or two, it will be ignored and the function will return a DB instance with a nil value for one or two. This could lead to a runtime panic when you try to use db.One.Where(...) or db.Two.Where(...) elsewhere in your code. Make sure to handle these errors appropriately.

  2. Dependency Injection: Instead of creating the gorm.DB instances within the NewDB() function, consider passing them as arguments to the function. This makes your code more flexible and testable, as you can pass different gorm.DB instances depending on the context (e.g., a real instance for production code, a mock instance for testing).

  3. Interface Abstraction: Consider defining an interface for the operations you want to perform on the database (e.g., Read, Write). Then, make sure your DB struct implements this interface. This allows you to abstract away the specific database technology (in this case, GORM) and makes your code more modular and easier to test.

Here's how you might modify your code to incorporate these suggestions:

package data

import (
    "errors"
    "time"

    "gorm.io/gorm"
    "gorm.io/gorm/logger"

    "example/internal/conf"
)

type DB struct {
    *gorm.DB

    One *gorm.DB
    Two *gorm.DB
}

type DBHandler interface {
    Where(...interface{}) *gorm.DB
}

func NewDB(def, one, two *gorm.DB) (*DB, error) {
    if def == nil || one == nil || two == nil {
        return nil, errors.New("nil database instance")
    }

    return &DB{
        DB:  def,
        One: one,
        Two: two,
    }, nil
}

In your code where you use the DB struct, you can now do something like this:

var dbHandler data.DBHandler = db
dbHandler.Where(...)
dbHandler.One.Where(...)
dbHandler.Two.Where(...)

This way, you're programming against an interface (DBHandler), not a concrete implementation (DB), which is a good practice in Go and in programming in general.

Remember, these are just suggestions and the actual implementation might vary based on your specific use case and requirements. I hope this helps! If you have any more questions, feel free to ask.

Sources

#### About Dosu This response is meant to be useful and save you time. It is **not meant to be a precise solution**, but rather a starting point for your own research. Help me be more useful! Please leave a 👍 if this is helpful and 👎 if it is irrelevant. If you want to continue the conversation, start your reply with @dosu-bot.