Weekly Review — Month 5 · Week 3 (Days 127–133)¶
📅 The Week in One Line¶
Moved from "a gRPC service that runs" to "a service built to last": hexagonal core, dependency injection (by hand and with wire), domain value objects/entities, ADRs for the why, and cache-aside with Redis.
✅ What I Completed¶
- Day 127 — Hexagonal architecture / ports & adapters (dependencies point inward)
- Day 128 — Manual dependency injection (constructor injection, composition root)
- Day 129 — DI with
wire(compile-time codegen: providers, sets, injectors,wire.Bind) - Day 130 — Domain modeling & boundaries (value objects vs entities, invariants)
- Day 131 — Architecture Decision Records (Nygard template, immutable log)
- Day 132 — Redis cache-aside (read-through, TTL, delete-on-write, stampede)
- Day 133 — Review + active recall
- Mini-project: stdlib models of a hexagonal core, hand-wired DI, value objects, and a TTL cache-aside cache
- Exercises solved: 3 (
ports,money,cacheaside) — all green
💡 Lessons Learned¶
- Define the interface next to its consumer. In Go the core owns the port and the adapter implements it; that single habit keeps the dependency arrow pointing inward and avoids import cycles.
- DI is just passing arguments. A constructor's parameter list is its dependency contract; inject interfaces (clock, repo, cache) to create test seams and prefer fakes over generated mocks.
wireis codegen, not a container. It emits the samemain-wiring I'd type by hand, fails at compile time, and adds zero runtime cost — andwire.Bindconnects a concrete to its port.- Make invalid states unrepresentable. Unexport fields, validate in the constructor, return new values from methods; comparable structs then give value equality for free, and money is integer cents, never float.
- ADRs preserve the why. Context + Consequences are what code can't tell you later; keep them short and immutable — supersede, never edit.
- Cache-aside keeps Redis out of the core. Read-through on miss, populate with a TTL, delete (don't rewrite) on write, branch on
redis.Nil, and never cache failures for long.
💪 Strengths (what clicked)¶
- The "core owns the port, adapter plugs in at the composition root" mental model — it generalized cleanly across the repo, the clock, and the cache.
- Value objects: validating constructor + unexported fields + immutability felt natural and made tests trivial.
🧩 Weaknesses (what's still fuzzy)¶
- Unit-of-work / transactions spanning two repository calls without leaking SQL into the core.
- Cache-stampede defenses (
singleflightvs probabilistic early expiry vs a Redis lock key). wireergonomics for two same-typed dependencies and cross-package provider sets.
🔁 Spaced-Repetition Re-quiz (topics from earlier weeks)¶
- Q: (M5 W2 — interceptors) In what order do you chain logging, recovery, and auth interceptors, and why?
A
Logging (outermost) → recovery → auth (innermost). Logging sees everything including recovered errors; recovery catches panics from auth and the handler; auth runs last before the handler so it can reject early. - Q: (M5 W1 — gRPC errors) Why return
status.Error(codes.X, msg)instead of a plain error?A
So callers can branch on the canonical code (status.Code(err)) and a gateway can map it to the right HTTP status — string-matching the message is brittle. - Q: (M4 — concurrency) What happens on a send/receive on a
nilchannel?A
It blocks forever. Useful to disable aselectcase by setting its channel to nil. - Q: (M3 — errors) How does
errors.Isdiffer from==?A
It walks the wrap chain viaUnwrap, matching a wrapped sentinel anywhere in the chain — e.g. anErrNotFoundwrapped with%w. - Q: (M1 — interfaces) Why can a non-nil interface hold a nil pointer and not equal
nil?A
An interface is (type, value); a nil*Tgives it a non-nil type word, soiface != nileven though the pointer is nil — the nil-interface trap.
🎯 Action Items¶
- Sketch a unit-of-work helper so a service can run two repository writes in one transaction without importing
database/sqlinto the core. - Add
singleflightto the cache-asideGetand benchmark it under a simulated stampede. - Write two real ADRs in
docs/adr/(gRPC-for-internal-APIs, hexagonal-layout) and number them. - Convert the hand-wired composition root into a
wireprovider set and diff the generated file.
🚀 Next Week Goals¶
- Observability: structured logging (
slog), request-scoped fields, and log levels. - Metrics (RED/USE), Prometheus exposition, and gRPC/HTTP instrumentation.
- Distributed tracing (OpenTelemetry) and context propagation across services.
- Health checks, readiness/liveness, and graceful shutdown.
📊 Metrics¶
| Hours | Days hit | Exercises | Commits | Avg confidence |
|---|---|---|---|---|
| 10.5 | 7/7 | 3 | 7 | 3.⅗ |
Suggested commit: docs(journal): month 5 week 3 review