A database-agnostic contract for Go services: Config (a PostgreSQL
connection, composable under an env prefix) and Transactor (the interface
services depend on to span multiple repositories in one transaction). The
root package has no database driver dependency of its own; a driver-specific
subpackage provides the actual implementation.
dbx has no gp-system dependency at all: the root package and seed are
standard-library only, pg links pgx and its own OTel tracer, and bunx links
bun. It works in any Go program. It is also the database layer used by the
gp-system backend kit.
go get github.com/gp-system/dbxOnly the subpackage(s) a project actually uses need to be imported:
go get github.com/gp-system/dbx/pg # pgx-native driver
go get github.com/gp-system/dbx/bunx # bun query-builder driver
go get github.com/gp-system/dbx/seed # seeder registry (driver-agnostic)
go get github.com/gp-system/dbx/seed/bunx # seeder first-or-create helper for bunOnly one driver is ever linked into a given binary; a service depends on
dbx.Transactor and dbx.Config, never on the concrete implementation.
pgis the pgx-native database stack:NewPool/MustNewPoolfor pool construction (with an OTel span per query viaotelpgx),DBTX/DBas the transaction-aware query executor repositories depend on, andNewTransactorproducing adbx.Transactorbacked bypgx.Tx.DBis method-for-method identical to theDBTXinterfacesqlcgenerates, so sqlc-based repositories use it unchanged.bunxis the optional adapter for projects that prefer the bun query builder:Openbuilds a*bun.DBover an existing pgx pool,From/Connresolve the query surface a repository should use (joining the transaction in context when one is open), andNewTransactorproduces adbx.Transactorbacked bybun.Tx. Nothing here links into a binary that does not import it, sopgstays bun-free.seedis the database-agnostic contract of the seeder subsystem: aRegistryof namedSeeders, each optionallyGuarded, run after migrations to load idempotent baseline data.WithTenantsruns every seeder once per tenant, with the tenant available viaTenantFromContext/OnlyTenants. It has no database driver dependency of its own.seed/bunxprovidesFirstOrCreate/FirstOrCreateWhere, the idempotency primitive seeders are built around, for projects using the bun query builder. A project that prefers raw SQL writes its ownINSERT ... ON CONFLICT DO NOTHINGdirectly againstdbx/pg'sDBTXinstead; the adapter is optional.
type Config struct {
DB dbx.Config `envPrefix:"DB_"`
}
pool := pg.MustNewPool(ctx, cfg.DB)
db := pg.NewDB(pool)
tx := pg.NewTransactor(pool)A service composes repositories inside one transaction:
tx.WithinTransaction(ctx, func(ctx context.Context) error {
if err := orders.Insert(ctx, o); err != nil {
return err
}
return inventory.Decrement(ctx, o.SKU, o.Qty)
})Both repositories resolve the same transaction from ctx (via
pg.TxFromContext/bunx.TxFromContext); an error from either rolls back
everything.
Seeding, wired up after migrations:
reg := &seed.Registry{}
reg.Add(AdminUser(deps))
reg.Add(DefaultRoles(deps))
if err := reg.Run(ctx); err != nil {
log.Fatal(err)
}- The root package has zero driver dependency.
dbx.Configanddbx.Transactordescribe the contract;pgandbunxare the only packages that import a database driver. A service depends on the root package's types, never onpgorbunxdirectly, so swapping drivers touches onlymainand the repository layer's constructors. - Only one driver is ever linked into a binary.
pgandbunxdo not import each other, and a project picks exactly one; mixing them is a build-time non-issue (nothing forces it) but a runtime footgun (the two transaction contexts are incompatible), documented onbunx. seedmirrors the same split. The registry and guard model inseedis driver-agnostic;seed/bunx.FirstOrCreateis the only piece that assumes bun, exactly likebunxversuspgfor the query layer itself.- Transaction failures are plain errors. Both
pg.NewTransactorandbunx.NewTransactorwrap the driver error withdbx.ErrBeginTxordbx.ErrCommitTx(via%w), so a caller distinguishes the failing step witherrors.Iswithout depending on any error-handling package.