Files
postgres/CLAUDE.md
Rene Nochebuena 2baafa6a0c feat(postgres): initial stable release v0.9.0
pgx v5-native PostgreSQL client with launcher lifecycle, health check, unit-of-work via context injection, and structured error mapping.

What's included:
- Executor / Tx / Client / Component interfaces using pgx native types (pgconn.CommandTag, pgx.Rows, pgx.Row)
- New(logger, cfg) constructor; pgxpool initialised in OnInit
- Config struct with env-tag support for all pool tuning parameters
- UnitOfWork via context injection; GetExecutor(ctx) returns active Tx or pool
- HandleError mapping pgerrcode constants to xerrors codes (AlreadyExists, InvalidInput, NotFound, Internal)
- health.Checkable at LevelCritical; HealthCheck delegates to pgxpool.Ping

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
2026-03-19 13:18:07 +00:00

3.9 KiB

postgres

pgx-backed PostgreSQL client with launcher lifecycle, health check integration, and unit-of-work transaction management.

Purpose

Provides a Component that manages a pgxpool connection pool, satisfies the launcher.Component lifecycle hooks (OnInit, OnStart, OnStop), and implements health.Checkable (priority: critical). Also provides NewUnitOfWork for wrapping multiple repository operations in a single transaction via context injection.

Tier & Dependencies

Tier 3 (infrastructure) — depends on:

  • code.nochebuena.dev/go/health (Tier 1)
  • code.nochebuena.dev/go/launcher (Tier 1)
  • code.nochebuena.dev/go/logz (Tier 0)
  • code.nochebuena.dev/go/xerrors (Tier 0)
  • github.com/jackc/pgx/v5 and pgxpool, pgconn sub-packages
  • github.com/jackc/pgerrcode

Key Design Decisions

  • pgx native types (ADR-001): Executor uses pgconn.CommandTag, pgx.Rows, and pgx.Row. There is no database/sql adapter. Repository code imports pgx types directly.
  • Local Executor interface (ADR-002): dbutil was eliminated. Executor, Tx, Client, and Component are all defined in this package using pgx types. The mysql module has its own independent Executor.
  • UnitOfWork via context injection (ADR-003): The active pgx.Tx is stored in the context under ctxTxKey{} (unexported). GetExecutor(ctx) returns the transaction if present, otherwise the pool. Tx.Commit and Tx.Rollback both accept ctx.
  • Error mapping via pgerrcode constants: HandleError uses pgerrcode.UniqueViolation, pgerrcode.ForeignKeyViolation, and pgerrcode.CheckViolation (not string-matched error messages) to map *pgconn.PgError to xerrors codes.
  • health.Checkable embedded in Component: pgComponent.HealthCheck delegates to Ping. Priority is health.LevelCritical — a PostgreSQL outage brings down the service.

Patterns

Lifecycle registration:

db := postgres.New(logger, cfg)
lc.Append(db)
r.Get("/health", health.NewHandler(logger, db))

Unit of Work across multiple repositories:

uow := postgres.NewUnitOfWork(logger, db)
err := uow.Do(ctx, func(ctx context.Context) error {
    exec := db.GetExecutor(ctx) // returns active Tx
    _, err := exec.Exec(ctx, "INSERT INTO ...")
    return err
})

Error handling in repository code:

row := db.GetExecutor(ctx).QueryRow(ctx, "SELECT ...")
if err := row.Scan(&out); err != nil {
    return db.HandleError(err) // maps pgx.ErrNoRows → ErrNotFound, etc.
}

What to Avoid

  • Do not use database/sql types (sql.Rows, sql.Result, etc.) alongside this module. The module is pgx-native; mixing the two type systems requires explicit adaptation.
  • Do not store Executor references across goroutine boundaries during a UnitOfWork.Do. The transaction is tied to the context passed into Do; goroutines that outlive Do will use a committed or rolled-back transaction.
  • Do not match PostgreSQL error codes by parsing error message strings. Use pgerrcode constants and errors.As(err, &pgErr) as HandleError does.
  • Do not add sync.Once or package-level pool variables. pgComponent is the unit of construction; create one per database and pass it as a dependency.
  • Do not call db.GetExecutor(ctx) from outside the UnitOfWork.Do callback if you need transactional semantics — it will return the pool.

Testing Notes

  • postgres_test.go tests compile-time interface satisfaction. Integration tests (pool, real queries) require a live PostgreSQL instance and are typically run in CI with a service container.
  • compliance_test.go asserts at compile time that postgres.New(...) satisfies postgres.Component.
  • HandleError can be unit-tested by constructing *pgconn.PgError with known Code values — no database connection needed.
  • Pool initialization happens in OnInit, not New. Tests that mock the Client interface bypass pool setup entirely.