By dropping down to common database/sql constructs, River can share connections and transactions with GORM, a well known ORM (Object Relational Mapper) in the Go ecosystem.
Sharing a database handle
The same *sql.DB handle can be configured on GORM and a River client:
import ( "database/sql"
"github.com/jackc/pgx/v5/pgxpool" _ "github.com/jackc/pgx/v5/stdlib" "github.com/riverqueue/river" "github.com/riverqueue/river/riverdriver/riverdatabasesql" "gorm.io/driver/postgres" "gorm.io/gorm")sqlDB, err := sql.Open("pgx", "postgres://localhost/river")if err != nil { return nil, err}
// Providing a separate listener pool is optional, but gives the client// access to listen/notify and more responsive to incoming jobs.listenerPool, err := pgxpool.New(ctx, "postgres://localhost/river")if err != nil { return nil, err}defer listenerPool.Close()
gormDB, err := gorm.Open(postgres.New(postgres.Config{ Conn: sqlDB,}), &gorm.Config{})if err != nil { return nil, err}
riverClient, err := river.NewClient( riverdatabasesql.NewWithPgxListener(sqlDB, listenerPool), &river.Config{ Workers: workers, },)if err != nil { return nil, err}Pgx provides LISTEN/NOTIFY
The database/sql package doesn't expose Postgres LISTEN, so NewWithPgxListener uses listenerPool to receive notifications while all queries and transactions continue through sqlDB.
Use riverdatabasesql.New instead to intentionally run in poll-only mode.
Sharing a transaction
Transactions are shareable by starting them from GORM, then unwrapping their underlying *sql.Tx with a type assertion and using it with a River client's InsertTx:
tx := gormDB.Begin()if err := tx.Error; err != nil { return nil, err}
// If in a transaction, ConnPool can be type asserted as an *sql.Tx so// operations from GORM and River occur on the same transaction.sqlTx := tx.Statement.ConnPool.(*sql.Tx)
_, err = riverClient.InsertTx(ctx, sqlTx, SortArgs{ Strings: []string{ "whale", "tiger", "bear", },}, nil)if err != nil { return nil, err}
if err := tx.Commit().Error; err != nil { return nil, err}