Lesson 1 · Hexagonal — the shape of a v2 module

The v2 map

Before any pattern: where "v2" lives, what the layers are called, and the one table that tells you which of the three patterns you'll actually find where.

Your win: open any module in your services and name its layers, and say out loud which of hexagonal / CQRS / sqlc you should expect to find there — and which you won't.

Two generations of code live side by side

Every service here has old code and new code in the same tree. The team calls the new shape "v2". You add new features to v2 and leave v1 alone:

GenerationLooks likeRule
v1 (legacy)entities/ repositories/ services/ subscriptions/ — a flat layer per kinddon't add new code here
v2 (active)modules/{name}/ — a self-contained bounded context per domainall new features go here

In eureka the new tree is literally internal/eureka/v2/; in spike and notification it's the modules/ folder. Same idea, two homes.1

The layers of a v2 module

A v2 module is layered so that the business logic sits in the middle and everything external — the database, gRPC, Kafka — plugs in at the edges. The names differ slightly by service, but the shape is identical:

┌─────────────── a v2 module ───────────────┐ │ controller / transport (grpc,http,kafka)│ ← inbound: thin, no logic │ │ │ │ application / usecase (commands,queries)│ ← business logic │ │ │ │ ⟶ PORT (interface) ⟵ │ ← what the core needs │ ▲ │ │ infrastructure / repository/postgres │ ← ADAPTER: real DB code │ │ │ domain (entities, rules) │ ← pure, at the centre └────────────────────────────────────────────┘

The whole course is really about that middle arrow — the port (an interface the core owns) and the adapter (a concrete implementation at the edge). Lesson 2 is entirely about it. For now, just learn to see the four bands.

The map: which pattern lives where

Here's the fact that saves you an hour of confusion. The three patterns in the course title are not evenly spread across the repo:

PatternLives inNOT in
Hexagonal (ports & adapters)spike, notification, conversationmgmt, eureka v2— (but adoption is uneven)
CQRS (commands / queries)spike, notification (system_notification)eureka (uses usecase/ + Reader/Writer split)
sqlc (typed SQL)eureka onlyspike, notification, conversationmgmt — they hand-roll SQL
The load-bearing fact for this whole course Your three services do hexagonal + CQRS but never sqlc. sqlc is eureka-only. So Parts 1 & 2 live in your code; Part 3 borrows eureka (the assessment module) for the one pattern your services don't use. If you go looking for a querier in spike, you won't find one — spike builds its SQL strings by hand, and that's by design.

One more thing: the vocabulary is split

The same architecture is described with two different dialects, and knowing this stops a lot of "wait, are these the same thing?" moments:

eurekaspike / notification
calls it"v2" / "clean architecture + DDD""the modules/ layer" / "hexagonal + CQRS"
layerstransport / usecase / domain / repositorycontroller / application / core (or domain) / infrastructure

This course uses hexagonal + CQRS + sqlc as the umbrella terms. When eureka's docs say "clean architecture," read "hexagonal"; they're the same family (Lesson 2).

The real spec is in .claude/rules/ The most precise description of v2 isn't in a readme — it's in .claude/rules/*.md (e.g. eureka-v2-conventions.md), which are auto-loaded and prescriptive: storage.go purity, querier injection, error translation. When a readme and a rule disagree, the rule wins.
Read this next

Combining DDD, CQRS & Clean Architecture in Go

Three Dots Labs — the single best article on exactly this stack in idiomatic Go. It's the public analogue of what your v2 modules are reaching for.

threedots.tech — DDD, CQRS & Clean Architecture combined
→ in-repo internal/spike/CLAUDE.md, internal/eureka/CLAUDE.md

Check yourself (from memory)

Q1. Which service uses sqlc?

sqlc is eureka-only. spike, notification, conversationmgmt build dynamic SQL by hand. Part 3 borrows eureka's assessment module for sqlc.

Q2. In a v2 module, where does the business logic sit?

Controllers are thin; adapters just talk to tech. Logic lives in application/ (spike) or usecase/ (eureka), between transport and ports.

Q3. eureka says "clean architecture"; spike says "hexagonal." What gives?

Clean / hexagonal / ports-and-adapters are the same family. The repo just names it differently per service. We use "hexagonal" throughout.
Recall: the v2 map — generations, layers, the pattern matrix.
v1 vs v2 + four layers + who-has-what, then reveal
Two generations: v1 legacy (entities/repositories/services/, don't touch) vs v2 active (modules/{name}/, all new code). eureka's v2 = literal internal/eureka/v2/. Four layers: controller/transport (thin) → application/usecase (logic) → port (interface) ← infrastructure/repository adapter; domain (pure) at the centre. The matrix: hexagonal = everywhere; CQRS = spike + notification; sqlc = eureka ONLY (your services hand-roll SQL). Naming split: eureka "v2 / clean arch / DDD"; spike+notification "modules / hexagonal / CQRS" — same shape. Real spec = .claude/rules/*.md.
🎯 Interview one-liner "How is your codebase structured?" → "Newer services follow a hexagonal, per-module layout — controller, application (commands/queries), a port interface, and an infrastructure adapter, with a pure domain at the centre. We adopted it incrementally, so v1 and v2 coexist; sqlc for typed SQL only landed in one service so far. It's pragmatic clean architecture, not dogmatic."
Next: the heart of it — ports & adapters, the one idea the whole architecture turns on. Ask me if the two-dialects thing is still fuzzy.

1. In-repo: internal/eureka/CLAUDE.md ("v2 Architecture"), internal/spike/CLAUDE.md ("Module Architecture"), internal/notification/CLAUDE.md. Full map with file:line in repo-v2-map.