Weekly Review — Month 4 · Week 2 (Days 092–098)¶
📅 The Week in One Line¶
Everything behind the HTTP handler: connecting to Postgres and pooling (database/sql, pgx/pgxpool), type-safe queries (sqlc), schema versioning (golang-migrate), and structuring it all behind a repository with atomic, context-aware transactions.
✅ What I Completed¶
- Day 092 —
database/sqlbasics: drivers, lazyOpen+Ping, the*sql.DBpool,Scan,rows.Err, placeholders, NULLs - Day 093 — pgx &
pgxpool: native vsdatabase/sqlmodes,Acquire/Releasevs direct pool calls, DSN, pool sizing - Day 094 — sqlc: write-SQL/generate-Go,
:one/:many/:execannotations, compile-time safety, no ORM - Day 095 — golang-migrate: ordered up/down files,
schema_migrations, idempotence,embed.FS, the dirty-state trap - Day 096 — repository pattern: consumer-side interface, domain errors, return copies, testable fakes
- Day 097 — transactions & context:
BeginTx, the deferred-RollbackWithTxhelper, isolation levels, cancellation - Day 098 — review + closed-book recall
- Examples: pool, migrate, repository, txn (stdlib-only, runnable)
- Exercises solved: 3 (migrationplan, inmemrepo, txstore)
💡 Lessons Learned¶
sql.Opendoes NOT connect — it's lazy;PingContextto verify connectivity at startup.- A
*sql.DB/pgxpool.Poolis a long-lived, concurrency-safe POOL; create once, share, and cap its size below Postgres'smax_connections. - After a
rows.Next()loop you must still checkrows.Err()—Next()==falsecovers both end-of-rows and a mid-iteration error. - sqlc is a code generator, not an ORM: real SQL in, type-safe Go out, with errors caught at generate-time.
- Never edit an applied migration; append a new one. A half-failed migration goes "dirty" and must be fixed then
forced. - Define repository interfaces on the consumer side ("accept interfaces, return structs"), translate driver errors into domain
ErrNotFound/ErrConflict, and return value copies — not pointers into the store. defer tx.Rollback()right afterBeginTxis the safety rule: it no-ops after a successfulCommitand undoes everything on any error/panic/timeout. Always checkCommit's error.- Run statements on
tx, notdb, or they hit different pooled connections and aren't atomic.
💪 Strengths (what clicked)¶
- The
WithTx(ctx, db, fn)deferred-Rollback pattern — it finally feels automatic. - Mapping driver errors to domain sentinels at the repository boundary and matching with
errors.Is. - The five-layer mental model: migrate → sqlc → pgx/pool → repository → transactions, all threaded by context.
🧩 Weaknesses (what's still fuzzy)¶
- Retrying serialization failures (
40001) under Repeatable Read / Serializable — number of attempts and backoff. - Dynamic SQL with sqlc (optional filters, variable-length
IN); when to drop to hand-written queries. - Connection poolers (PgBouncer) and which pgx features (prepared statements, session state) break in transaction-pooling mode.
🔁 Spaced-Repetition Re-quiz (topics from earlier weeks)¶
- Q: (M1) What does
string(65)produce and why?A
"A"— int→rune to code point U+0041, not"65". Usestrconv.Itoafor the digits. - Q: (M2) What does
%winfmt.Errorfdo?A
Wraps an error soerrors.Is/errors.Ascan unwrap and match it — the basis for this week's domain-error translation. - Q: (M3 W2) Why must
wg.Addprecedego?A
Addinside the goroutine races withWait, which could return early on a still-zero counter. - Q: (M4 W1) Set
Content-Typebefore or afterWriteHeader?A
Before — headers freeze once the body starts flushing; setting it after is silently ignored. - Q: (M4 W2) Does
sql.Openestablish a connection?A
No — it's lazy; the first connection happens on first use. Calldb.PingContextto verify eagerly.
🎯 Action Items¶
- Write a generic
retryTxwrapper that retries on Postgres40001with bounded attempts + jittered backoff. - Rebuild the Week 1 widgets API's storage layer on a
pgxpool.Pool+ a repository, swapping the in-memory store with no handler changes. - Add embedded
embed.FSmigrations run at startup, and verify idempotence by booting twice. - Spike sqlc's
sqlc.slice()/= ANY($1)for variable-lengthINfilters.
🚀 Next Week Goals¶
- Assemble the pieces into a real service: configuration, structured logging, and observability around the data layer.
- Test the data layer: in-memory fakes for unit tests, and integration tests against a throwaway Postgres.
- Wire request context end-to-end: HTTP handler → service → repository → transaction, with timeouts and cancellation.
📊 Metrics¶
| Hours | Days hit | Exercises | Commits | Avg confidence |
|---|---|---|---|---|
| 9 | 7/7 | 3 | 7 | 3.⅗ |
Suggested commit: docs(journal): month 4 week 2 review