Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dbx

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.

Install

go get github.com/gp-system/dbx

Only 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 bun

Only one driver is ever linked into a given binary; a service depends on dbx.Transactor and dbx.Config, never on the concrete implementation.

Subpackages

  • pg is the pgx-native database stack: NewPool/MustNewPool for pool construction (with an OTel span per query via otelpgx), DBTX/DB as the transaction-aware query executor repositories depend on, and NewTransactor producing a dbx.Transactor backed by pgx.Tx. DB is method-for-method identical to the DBTX interface sqlc generates, so sqlc-based repositories use it unchanged.
  • bunx is the optional adapter for projects that prefer the bun query builder: Open builds a *bun.DB over an existing pgx pool, From/Conn resolve the query surface a repository should use (joining the transaction in context when one is open), and NewTransactor produces a dbx.Transactor backed by bun.Tx. Nothing here links into a binary that does not import it, so pg stays bun-free.
  • seed is the database-agnostic contract of the seeder subsystem: a Registry of named Seeders, each optionally Guarded, run after migrations to load idempotent baseline data. WithTenants runs every seeder once per tenant, with the tenant available via TenantFromContext/OnlyTenants. It has no database driver dependency of its own.
  • seed/bunx provides FirstOrCreate/FirstOrCreateWhere, the idempotency primitive seeders are built around, for projects using the bun query builder. A project that prefers raw SQL writes its own INSERT ... ON CONFLICT DO NOTHING directly against dbx/pg's DBTX instead; the adapter is optional.

Usage

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)
}

Design rules

  • The root package has zero driver dependency. dbx.Config and dbx.Transactor describe the contract; pg and bunx are the only packages that import a database driver. A service depends on the root package's types, never on pg or bunx directly, so swapping drivers touches only main and the repository layer's constructors.
  • Only one driver is ever linked into a binary. pg and bunx do 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 on bunx.
  • seed mirrors the same split. The registry and guard model in seed is driver-agnostic; seed/bunx.FirstOrCreate is the only piece that assumes bun, exactly like bunx versus pg for the query layer itself.
  • Transaction failures are plain errors. Both pg.NewTransactor and bunx.NewTransactor wrap the driver error with dbx.ErrBeginTx or dbx.ErrCommitTx (via %w), so a caller distinguishes the failing step with errors.Is without depending on any error-handling package.

About

Database-agnostic Config and Transactor contract for Go services, with pgx and bun driver subpackages.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages