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/
This commit is contained in:
70
CLAUDE.md
Normal file
70
CLAUDE.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 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:
|
||||
|
||||
```go
|
||||
db := postgres.New(logger, cfg)
|
||||
lc.Append(db)
|
||||
r.Get("/health", health.NewHandler(logger, db))
|
||||
```
|
||||
|
||||
Unit of Work across multiple repositories:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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.
|
||||
Reference in New Issue
Block a user