Installation
The module, the CLI and the configuration file.
The module#
go get github.com/AlexAli29/ormThe runtime depends only on the standard library and github.com/jackc/pgx/v5. That boundary is enforced by a test in the suite, so it cannot rot.
The CLI#
The generator and the migration planner are one binary:
go install github.com/AlexAli29/orm/cmd/orm@latestOr run it without installing, which is what most projects put in their Makefile:
go run github.com/AlexAli29/orm/cmd/orm generatePin it in tools.go if you want the version tracked with everything else.
Optional adapters#
Each is a module of its own, so a project that does not use one never compiles it:
go get github.com/AlexAli29/orm/ormotel # OpenTelemetry tracing
go get github.com/AlexAli29/orm/ormtest/postgres # Testcontainers helpersormslog, ormhealth and ormtest live in the core module and cost nothing until imported.
orm.yaml#
The configuration file sits at the project root:
version: 1
schema:
# Managed: the declarations own the schema and migrations apply it.
# Omit `mode` for database-first, where the database is authoritative.
mode: managed
dsn: ${DATABASE_URL}
search_path:
- public
migrations:
dir: migrations
packages:
- path: ./internal/domain
output: same
# Types Go has no equivalent for reach it through configuration. The ORM
# refuses to choose a uuid package for you, because the popular ones are not
# interchangeable.
types:
uuid:
go: github.com/google/uuid.UUID
codec: uuid${DATABASE_URL} is expanded from the environment, so the file carries no credentials and can be committed.
Verifying the install#
orm checkWith an empty project this reports that it found no declarations, which is the correct answer and proves the CLI can reach the database.
Worked examples#
Database-first, against an existing database#
No mode, no migrations directory — the database is authoritative and you write
declarations that describe it:
version: 1
schema:
dsn: ${DATABASE_URL}
search_path:
- public
- reporting
packages:
- path: ./internal/domain
output: sameManaged, with several bounded contexts#
Each context owns its own package, and the generator writes beside each:
version: 1
schema:
mode: managed
dsn: ${DATABASE_URL}
search_path:
- public
- billing
- identity
migrations:
dir: migrations
packages:
- path: ./internal/billing/domain
output: same
- path: ./internal/identity/domain
output: same
- path: ./internal/catalog/domain
output: sameTwo contexts may own tables with the same name in different schemas; they produce separate descriptors and separate migration state.
Types Go does not have#
types:
uuid:
go: github.com/google/uuid.UUID
codec: uuid
numeric:
go: github.com/shopspring/decimal.Decimal
codec: decimalThese are the two the ORM refuses to choose for you, because the popular packages
are not interchangeable and a wrong numeric silently corrupts money.
A Makefile that keeps everything in step#
generate:
go run github.com/AlexAli29/orm/cmd/orm makemigrations
go run github.com/AlexAli29/orm/cmd/orm migrate
go run github.com/AlexAli29/orm/cmd/orm generate
check:
go run github.com/AlexAli29/orm/cmd/orm makemigrations --check
go run github.com/AlexAli29/orm/cmd/orm check --generatedRunning the CLI from a container#
If you would rather not install Go on a CI runner, the CLI is published as an image. There are two, because the commands differ in what they need:
$ docker run --rm -v "$PWD":/work -e DATABASE_URL \
ghcr.io/alexali29/orm:latest migrate --config /work/orm.yaml
Applying 0001_initial ... OKghcr.io/alexali29/orm is the CLI on distroless — about 16 MB, no shell, no
libc, running as a non-root user. It covers migrate, sqlmigrate and
inspect, which is the deploy-time set.
ghcr.io/alexali29/orm:latest-toolchain adds Go. check, generate and
makemigrations read your entity source through go list, so they need a
toolchain and a module that resolves — and mounting your module is not enough if
its dependencies are not there:
$ docker run --rm -v "$PWD":/work -e DATABASE_URL \
ghcr.io/alexali29/orm:latest-toolchain check --config /work/orm.yaml
reconciliation clean: the entities and the schema agreeAsking the small image for one of those three fails immediately and says why —
go command required, not found — rather than producing a wrong answer. That is
the reason the split exists rather than one image that sometimes works.
Both are built for linux/amd64 and linux/arm64. Tags follow releases:
1.2.3, 1.2, latest, and edge for the tip of main.
In a pipeline#
migrate:
image: ghcr.io/alexali29/orm:latest
script:
- orm migrate --config orm.yamlThe entrypoint is the binary, so the arguments are the command. Nothing needs a shell, which is worth having in a step whose environment holds a database URL.