Project layout
The layout follows one rule: code is grouped by what it is about, not by its technical layer. A service is one folder under internal/, and its binary is a thin main.go under cmd/.
cmd/ auth/ main.go, store_*.go user/ main.go, store_*.gointernal/ platform/ the shared kit auth/ tokens, OAuth2, JWKS user/ accounts, event-sourcedproto/ Connect contractsgen/ generated Connect codeapi/ OpenAPI filesweb/ the web app and gatewaye2e/ end-to-end testsdeploy/ the Helm chart, compose extraswebsite/ this sitedocs/standards/.agents/skills/What each folder holds
Section titled “What each folder holds”| Folder | Holds |
|---|---|
cmd/ |
The composition root, kept under 150 lines. It loads config, opens the store and starts the servers. <service> migrate applies migrations or indexes and exits; <service> env prints the configuration reference; <service> healthcheck asks the local /, for container health checks. |
cmd/ |
Opens the chosen backend and returns its stores, readiness checks and migrate function. Apart from the end-to-end suite, these are the only files with build tags. |
internal/ |
The kit both services build on. Each package has one job: es (event sourcing), outbox, uow (transactions), apperr (error kinds), memdb, mysql and mongodb (backends), httpx, authn, connectx, config, obs and run. |
internal/ |
The event-sourced service: domain (the aggregate and its events), app (use cases), store (its contract suite and one package per backend), mail, httpapi, and rpc (the Connect server auth calls). |
internal/ |
The state-stored token service: app (sessions, OAuth2 clients, grants and consent), keys (signing keys, JWKS and tokens), store (its contract suite and one package per backend) and httpapi. It calls the user service through the generated client in gen/. |
proto/ |
Connect service definitions, linted and checked for breaking changes with buf. |
gen/ |
Code generated from proto/. It is committed, so a build needs no generator. |
api/ |
One OpenAPI 3.1 file per service. A test checks each against its router. |
web/ |
The Vite app. Its nginx image is also the gateway, locally and in production. |
e2e/ |
End-to-end tests that drive the running stack through the gateway, built with the e2e tag. |
deploy/ |
The Helm chart in helm/, for both services and the web app, and the files the compose files mount in compose/. |
docs/ |
The code bar every change meets: shared rules, Go and TypeScript. |
.agents/ |
Checklists for coding agents: add a command, an endpoint, a service. |
Import rules
Section titled “Import rules”These are checked by a test in internal/, so breaking one fails the build:
internal/imports no service package.platform internal/imports only the standard library,user/ domain internal/andplatform/ es internal/. The domain can be read and tested without any infrastructure.platform/ apperr - Services never import each other. They share only
internal/andplatform gen/, the generated Connect stubs. - Test helpers, the
…testpackages and testcontainers, are never imported by production code.
Build tags
Section titled “Build tags”Each binary links exactly one store. The tags are persistence_mysql and persistence_mongodb; memory is the default, built when neither is set. Tags appear only in cmd/, never inside a package, so every package compiles the same way whatever store you pick. CI builds and tests all three variants.
go build -tags persistence_mysql ./cmd/user