Flow
Decisions

0011 — Generated from an entity spec

One YAML entity spec per entity; migrations, handlers, and events are generated.

Decided. A YAML entity spec is the source of truth for the boring parts. The migration, typed queries, handlers, event payloads, event_types rows, bus topology, OpenAPI, and authz wiring are generated from it and regenerated on every build. CI fails if the generated files drift.

Alternatives

  • Scaffold once, then hand-edit. What most generators do, and the output rots the first time someone edits it.
  • No generation, conventions and code review. Works until the tenth service.
  • A framework (Encore or similar). More leverage, much more lock-in.

Why generate-always rather than scaffold-once

A generator you run once produces code nobody trusts, because there is no way to tell whether it has been edited. Regenerating on every build with a git diff --exit-code check in CI makes generated files provably generated. Hand editing one turns the build red.

What stays human

Domain logic. internal/domain/<entity>.go — pure functions over current state, returning new state plus events. No SQL, no transaction, no publish, testable without a database.

The generator owns the sequence that is easy to get subtly wrong: claim the idempotency key, SELECT ... FOR UPDATE, validate, write, insert the event row, commit, with no remote calls inside the transaction.

Two rules that keep it from being routed around

  • Escape hatches everywhere. Hand-written SQL in query.sql beside generated queries. A hand-written handler that still uses the generated executor. The spec covers the boring 80%; the rest is ordinary Go.
  • The generator never owns a file a human edits. Generated files are read-only and rewritten; human files are never touched after creation. No merge markers, no "edit below this line."

Build order

The generator is built fourth, not first. Building it first means encoding guesses about code that does not exist yet. Instead the golden service is written by hand, as though a generator had produced it, and FRICTION.md records every repetitive step. That file is the generator's requirements.

Acceptance test: regenerating the golden service reproduces it, and the diff is empty.

Consequences

  • The entity spec is a homegrown DSL and will meet cases it cannot express. Watch for specs with more exceptions than declarations — that is the signal the DSL is wrong, not that the engineer is.
  • The template is a real service in the repo that CI builds and tests, not .tmpl files nobody compiles. A rotted template fails CI on its own.

On this page