package appview import ( "fmt" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/plugin/dbresolver" ) // registerReadReplica wires the dbresolver plugin to route reads to // cfg.DatabaseReaderURL when that is non-empty. Writes always stay on the // primary opened by the caller. When the config is empty the function is // a no-op so single-DB deployments keep their existing behavior. // // Transactions and locked reads (FOR UPDATE) route to the primary via // dbresolver's default policy. Replica lag is a real concern — callers // that must read-their-own-write should wrap the sequence in a // transaction, which pins the whole block to the primary. func registerReadReplica(db *gorm.DB, cfg Config) error { if cfg.DatabaseReaderURL == "" { return nil } resolver := dbresolver.Register(dbresolver.Config{ Replicas: []gorm.Dialector{postgres.Open(cfg.DatabaseReaderURL)}, Policy: dbresolver.RandomPolicy{}, }) // Apply the same pool shape to the replica so saturation behavior // stays predictable across both databases. resolver = resolver. SetMaxOpenConns(cfg.DBMaxOpenConns). SetMaxIdleConns(cfg.DBMaxIdleConns). SetConnMaxLifetime(cfg.DBConnMaxLifetime). SetConnMaxIdleTime(cfg.DBConnMaxIdleTime) if err := db.Use(resolver); err != nil { return fmt.Errorf("install dbresolver: %w", err) } return nil }