37 lines
2.5 KiB
Markdown
37 lines
2.5 KiB
Markdown
|
|
# ADR-002: Local Executor Interface
|
||
|
|
|
||
|
|
**Status:** Accepted
|
||
|
|
**Date:** 2026-03-18
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
The `Executor` interface — the common query interface shared by the connection pool and an active transaction — must be defined somewhere. Earlier iterations of this codebase placed it in a shared `dbutil` package that both `postgres` and `mysql` imported. This created a cross-cutting dependency: every database module depended on `dbutil`, and `dbutil` had to make choices (e.g., which type system to use) that were appropriate for only one of them.
|
||
|
|
|
||
|
|
`dbutil` was eliminated as part of the monorepo refactor (see `plan/decisions.md`).
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
The `Executor` interface is defined locally inside the `postgres` package:
|
||
|
|
|
||
|
|
```go
|
||
|
|
type Executor interface {
|
||
|
|
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
|
||
|
|
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
||
|
|
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The `mysql` package defines its own separate `Executor` using `database/sql` types. The two are not interchangeable by design — they represent different type systems.
|
||
|
|
|
||
|
|
`Tx` extends `Executor` with `Commit(ctx context.Context) error` and `Rollback(ctx context.Context) error`. `Client` provides `GetExecutor`, `Begin`, `Ping`, and `HandleError`. `Component` composes `Client`, `launcher.Component`, and `health.Checkable`.
|
||
|
|
|
||
|
|
Repository code in application layers should depend on `postgres.Executor` (or the higher-level `postgres.Client`) — not on the concrete `*pgxpool.Pool` or `pgTx` types.
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
- **Positive**: No shared `dbutil` dependency. Each database module owns its interface and can evolve it independently.
|
||
|
|
- **Positive**: The interface methods use pgx-native types, so there is no impedance mismatch between the interface and the implementation.
|
||
|
|
- **Positive**: Mocking `postgres.Executor` in tests requires only implementing three methods with pgx return types — no wrapper types needed.
|
||
|
|
- **Negative**: If a project uses both `postgres` and `mysql`, neither module's `Executor` is compatible with the other. Cross-database abstractions must be built at the application domain interface layer, not by sharing a common `Executor`.
|
||
|
|
- **Note**: `pgComponent` itself also implements `Executor` directly (forwarding to the pool), which means a `*pgComponent` can be used wherever an `Executor` is expected without calling `GetExecutor`. This is intentional for ergonomics in simple cases where no transaction management is needed.
|