- Docs
- Concepts
- Backends
Backends
How pluggable codegen backends turn a resolved schema into ORM bindings, protobuf, OpenAPI, and migrations.
Backends are the codegen extension point: the frontend resolves *ir.Schema, and each backend turns it into files. Use them to generate typed bindings, protobuf, OpenAPI, or migration HCL. Business logic and adapter wiring stay hand-written, because backends emit scaffolding, not behavior.
The mental model
Section titled “The mental model”The backend package holds the interface only. compile.Compile parses filename-sorted inputs, resolves once, then calls each backend’s Generate in order.
type Backend interface { Name() string Generate(schema *ir.Schema) (map[string][]byte, error)}Backends read only *ir.Schema, never source, and take no config struct. Select them at the compile.Compile call or with --backend, which filters which Generate runs (omit it to run all except mcp).
Run the backends
Section titled “Run the backends”zever compile --out ./generatedzever compile --backend=atlas,gogen,mcp,openapi,proto,protogogen,zenorm --out ./generatedls ./generated./generated holds one subtree per backend. Run zever check schema/ first: backends assume a resolved schema.
The seven backends
Section titled “The seven backends”| Backend | Reads | Emits |
|---|---|---|
zenorm |
schema.Modules[*].Entities only |
Typed table and column bindings |
gogen |
Modules and services | Application layer (handlers, wiring) |
proto |
Modules, enums, RPCs | Protobuf descriptors |
protogogen |
Modules, enums, RPCs | zever.app.v1 protobuf and gen/zever/app Go |
openapi |
Services and HTTP bindings | OpenAPI document |
atlas |
Modules and entities | Atlas HCL migration files, one per module |
mcp |
Services | MCP tool manifests (JSON Schema in and out) and a runnable stdio server with handler stubs |
The canonical app.zen fixture exercises the first six. Both entities compile under the virtual path schema/app/app.zen (module app), so the User <-> Order relation needs no import. Adding services never affects zenorm output.
dsl/naming converts names per target: APIKey becomes api_key in Postgres and SQLite table names (acronym runs stay one word). SQL always binds args and quotes identifiers; dialect gates live with the ORM.
API documents and client SDKs
Section titled “API documents and client SDKs”The openapi backend already emits an OpenAPI 3.0.3 document from the services’ HTTP bindings, and proto plus protogogen emit the protobuf contract for gRPC. Zever does not ship its own client-SDK generator. Feed those artifacts to the generator for your language instead: an OpenAPI generator (for example openapi-generator, oapi-codegen, or openapi-typescript) over the document under generated/openapi for REST clients, and buf generate (or protoc) with the language plugin over generated/proto for gRPC and Connect clients. Because both outputs are regenerated and drift-checked by zever compile --check, a client built from them stays in step with the schema.
Keep output deterministic
Section titled “Keep output deterministic”Same inputs must give the same bytes on every run.
| Rule | Why |
|---|---|
| Sort map keys before rendering | Go map order is random per run |
| Pin timestamps (no clock reads in output) | Same inputs must give the same bytes |
| Repo-relative output paths | Outputs diff cleanly across machines |
| One subtree per backend | --backend= filtering stays trivial |
| No network or unseeded randomness | Hermetic codegen |
Write a backend
Section titled “Write a backend”- Implement
Backendwith a stableName(). - Read only
*ir.Schema; never re-parse source. - Return a path-to-bytes map with repo-relative paths and deterministic rendering. Implement
ContextBackend(GenerateContext) only if generation does I/O that must honor cancellation. - Add a golden test in the backend’s package (
dsl/backend/<name>/) with committed output.
Golden workflow: edit .zen, run zever check, run compile, and review the full golden diff as the executable spec of the change. Accept with -update only after review:
go test ./dsl/backend/gogen/ -run TestGoldenFiles -updateUnrelated golden churn means a backend leaks non-determinism.
Design decisions and trade-offs
Section titled “Design decisions and trade-offs”- Never hand-edit
generated/: the next compile overwrites it. Fix the.zensource. - Pin
--backend=...in CI so a new backend cannot change outputs silently. - Keep backends pure: environment specifics belong in adapter options.
Troubleshooting
Section titled “Troubleshooting”Generated output changed unexpectedly
Cause: a schema edit or keyword change. Fix: diff against the committed goldens, then revert the schema or accept the golden with -update after review.
A backend is missing from the output
Cause: a misspelled --backend= filter. Fix: use the lowercase names atlas, gogen, openapi, proto, protogogen, zenorm.
CI shows non-deterministic diffs
Cause: a backend iterates a map without sorting or embeds a timestamp. Fix: sort keys and pin times.
protogogen fails running protoc-gen-go-grpc
Cause: it shells out to go tool protoc-gen-go-grpc, which needs a working go.mod and go.sum. Fix: run go mod tidy in the project, then compile again.
Generate errors on an unknown type
Cause: the schema failed binding. Fix: run zever check first.