dpn517111 2017-01-12 16:44
浏览 47

模式交换支持Go GORM

I'm writing a REST API in which I need to dynamically deploy connections to multiple schemas.

Example: I have two schemas in which I need to change depending on which user is trying to consume the data.

The idea is to have many other schemes, one for each user.

I saw this issue but the example used is static needed to find a way to dynamically change. I'll put the user schema in the JWT token.

My project: Panda-API

Any suggestion?

  • 写回答

1条回答 默认 最新

  • donjd86266 2017-01-12 19:04
    关注

    OPTION 1

    You can change the database.GetConnection() method to receive the username and connect directly to the database without having to change all services and models. You're storing the user in the gin.Context object on security_middleware.go, so you can get it from there on the controllers and pass it to the services so they get the corresponding DB connection.

    But for this, you have to remove the Singleton pattern you have to store the DB object and create a pool of DB objects, maybe in a map[string]*DB and instead of caching the DB object in the services package, you cache all user-DB objects in the database package.

    Your database/database.go file will look something like:

    // Add sync import to handle concurrent access to the cache
    import "sync"
    
    // ... existent code
    
    // DB objects cache
    type DBs struct {
        Cache map[string]*gorm.DB
        sync.RWMutex
    }
    
    var dbs *DBs
    
    // Init cache
    func init() {
        dbs = DBs{
            Cache: make(map[string]*gorm.DB)
        }
    }
    
    func GetConnection(username string) *gorm.DB {  
        // Try to get connection from the cache
        dbs.RLock()
        if db, ok := dbs.Cache[username]; ok {
            dbs.RUnlock()
            return db
        }
    
        // Figure out DB_NAME dynamically here, based on username...
        //...
        dbName := figuredOutDB_NAME    
    
        db, err := gorm.Open(DB_DATABASE, "host=" + DB_HOST + " user=" + DB_USER + " dbname=" + dbName + " sslmode=" + DB_SSL_MODE + " password=" + DB_PASSWORD)
    
        if err != nil {
            panic(err)
        }
    
        //Ativa log de todas as saidas da conexão (SQL)
        db.LogMode(GetENVLogMode())
        //Seta o maximo de conexões
        db.DB().SetMaxIdleConns(DB_MAX_CONNECTION)
        db.DB().SetMaxOpenConns(DB_MAX_CONNECTION)
    
        DropTablesIfExists(db)
        AutoMigrate(db)
        AutoPopulate(db)
        AddForeignKeys(db)
    
        // Save connection to cache
        dbs.Lock()
        dbs.Cache[username] = db
        dbs.Unlock()
    
        return db
    }
    
    // ... and so on
    

    Then remove the services/services.go file as it would be useless. And change your services methods to receive the username as a param and instead of using the Con variable, call Con := database.GetConnection(username) every time.

    I hope that gives you an idea of a possible solution. Of course there may be other options, but that's what i can think on right now.

    The problem i see with this method is that you'll have one connection open (and a gorm.DB object) for each user in the system, not sure how many users you're expecting, but it can be a problem.

    OPTION 2

    Another solution is to follow the same changes on the services so they receive the user as a param on all methods, but instead of getting a new connection, set the username/db name to a custom model property that you can use to implement your own Model.TableName() method that uses that property to return the schema.table format.

    So you change your models to have a private property with a setter, like:

    type Person struct {
       schemaName string
    
       // ... existent properties.
    }
    
    func (p *Person) SetUser(u string) string {
        // Figure out the schema name from the username
        //...
    
        p.schemaName = schema
    }
    
    func (p *Person) TableName() string {
        return p.schemaName + ".persons"
    }
    

    Then, on your services you set the user every time you create a new model instance:

    func GetPeople(pag helpers.Pagination, q url.Values, username string) models.People {
    
        var people models.People
        (&people).SetUser(username)
    
        db := Con
    
        // ... and so on
    

    These are 2 possible solutions i can think on now. There may be more and better, but hope that helps.

    评论

报告相同问题?

悬赏问题

  • ¥20 求个正点原子stm32f407开发版的贪吃蛇游戏
  • ¥15 正弦信号发生器串并联电路电阻无法保持同步怎么办
  • ¥15 划分vlan后,链路不通了?
  • ¥20 求各位懂行的人,注册表能不能看到usb使用得具体信息,干了什么,传输了什么数据
  • ¥15 个人网站被恶意大量访问,怎么办
  • ¥15 Vue3 大型图片数据拖动排序
  • ¥15 Centos / PETGEM
  • ¥15 划分vlan后不通了
  • ¥20 用雷电模拟器安装百达屋apk一直闪退
  • ¥15 算能科技20240506咨询(拒绝大模型回答)