Weekly Review — Month 4 · Week 4 (Days 106–112)¶
📅 The Week in One Line¶
Built the Month 4 capstone — a layered, tested, documented, containerised REST service — and shipped v0.4.0.
✅ What I Completed¶
- Day 106 — Layered architecture: inward-pointing layers, consumer-owned interfaces
- Day 107 — Handlers & services: thin transport, rule-owning service, JSON helpers
- Day 108 — Repository & migrations: SQL adapter hides the driver behind domain errors
- Day 109 — Integration tests:
httptest+ testcontainers, build-tag gated - Day 110 — OpenAPI docs: 3.0 document,
$refschemas, served/openapi.json - Day 111 — Dockerfile & CI: multi-stage static image,
go test -racepipeline - Day 112 — Month 4 review +
v0.4.0tag - Mini-project: layered Users/Notes REST API (capstone)
- Exercises solved: 3 (service, httpapi, openapi)
💡 Lessons Learned¶
- Interfaces belong with their consumer; that one rule makes the whole stack swappable and testable.
- The repository is the single place driver errors (
sql.ErrNoRows) become domain errors (ErrNotFound); the handler is the single place domain errors become HTTP status. httptest.NewServerlets you integration-test the real stack with no extra dependency; testcontainers only swaps in a real database — the driving loop is identical.- A multi-stage build +
CGO_ENABLED=0+ distroless turns a 900 MB image into ~15 MB and removes the shell from prod. - OpenAPI is just JSON; modelling it as structs gives deterministic, diffable output you can test.
💪 Strengths (what clicked)¶
- Wiring layers bottom-up with constructor injection felt natural after the repository/txn examples from Weeks 2–3.
- Table-driven handler tests with
httptest.NewRecorderare fast and read well.
🧩 Weaknesses (what's still fuzzy)¶
- pgx-native API vs
database/sqlcompatibility layer — when each is worth it. - Parallelising integration tests safely (schema-per-test vs transaction-per-test).
🔁 Spaced-Repetition Re-quiz (topics from earlier weeks)¶
- Q: (Wk1) What does Go 1.22's
ServeMuxdo automatically when a path matches but the method doesn't?A
Returns405 Method Not Allowed(with anAllowheader) instead of falling through to 404. - Q: (Wk2) Why is
*sql.DBlong-lived and shared rather than opened per request?A
It's a connection pool, safe for concurrent use; opening one per request defeats pooling and exhausts connections. - Q: (Wk3) When do you return 400 vs 422 for a bad request body?
A
400 for malformed/unparseable JSON; 422 for well-formed JSON that fails validation rules. - Q: (Wk3) How do you compare an HMAC/JWT signature without leaking timing?
A
hmac.Equal, a constant-time comparison — never==on the bytes. - Q: (this week) Where should
sql.ErrNoRowsbe translated and to what?A
In the repository, wrapped to a domainErrNotFoundvia%w, so callers stay driver-agnostic and match witherrors.Is.
🎯 Action Items¶
- Add
govulncheckto the CI workflow. - Document the JWT
securitySchemein the OpenAPI spec. - Benchmark pgx-native vs
database/sqlfor the hot read path.
🚀 Next Week Goals¶
- Start Month 5 (theme TBD in the month review): pick the project, read the roadmap, scaffold day 113.
- Carry the layered-architecture habits forward into the new project.
📊 Metrics¶
| Hours | Days hit | Exercises | Commits | Avg confidence |
|---|---|---|---|---|
| 10.5 | 7/7 | 3 | 7 | 3.⅗ |
Suggested commit: docs(journal): week 4 review