Application database access should make query inputs, output shape, transaction ownership, tenant scope, timeouts, and error behavior explicit. Python’s psycopg and TypeScript’s node-postgres both support server parameters and pooling; neither automatically provides authorization, schema compatibility, idempotency, safe retries, or correct transaction boundaries. Keep domain operations narrow and observable.
Use four layers: route/handler authenticates; application service owns the use case/transaction; repository or query module executes known SQL; PostgreSQL enforces durable invariants. Avoid an abstraction that hides SQL semantics and avoid SQL scattered through controllers.
Production code validates returned row shape, configures connect/statement/lock timeouts, maps SQLSTATEs to safe domain errors, and never returns raw database messages to untrusted clients.
Release database connections before model calls. Validate model output into typed commands, reacquire a connection, set tenant context, recheck state/version, and commit briefly. Trace query fingerprints and timings without logging customer data or vectors.
Integration tests cover success, missing row, other tenant, constraint violation, timeout, pool exhaustion, rollback, and dropped connection. Assert the pool returns to baseline and no tenant setting leaks between transactions.
Implement resolve_ticket(expected_version) in one language with a conditional update and audit event.
Acceptance criterion: success is atomic, stale/other-tenant cases change nothing, connections release on all paths, and model calls are outside the transaction.