Pop — Structure#

Layout pattern#

Custom / Flat Root + Sub-packages for Concerns

Pop does not follow the canonical Standard Go Layout (cmd/, internal/, pkg/). Instead, the entire ORM core lives flat in the root package (github.com/gobuffalo/pop/v6), with a handful of well-defined sub-packages for cross-cutting concerns (associations, columns, slices, logging) and a soda/ directory that acts as a self-contained CLI application. The internal/ directory contains only two tiny utility packages. This flat-root style is common in library-first projects where the root package is the primary public API.

Directory map#

repositories/pop/
├── *.go                    # Root package: core ORM (connection, query, model, dialects, migrator)
├── associations/           # Association logic (belongs_to, has_many, has_one, many_to_many)
├── columns/                # Struct reflection helpers: mapping struct fields → SQL columns
├── fix/                    # Test fixture loader (anko scripting + auto-timestamp controls)
├── genny/                  # Code generation templates (config, fizz migrations, model scaffolding)
│   ├── config/             #   - database.yml template
│   ├── fizz/               #   - Fizz migration file templates (empty/table variants)
│   └── model/              #   - Go model scaffolding templates
├── internal/               # Private utilities
│   ├── defaults/           #   - String/int default value helpers
│   └── randx/              #   - Random string generation
├── logging/                # Logging constants (log level definitions)
├── slices/                 # Typed slice helpers (string, int, float, UUID, map) for scan targets
├── soda/                   # CLI binary (database management tool)
│   ├── main.go             #   - Entry point
│   └── cmd/                #   - Cobra command implementations
│       ├── root.go         #     · Root command, env/config flags
│       ├── create.go       #     · db create
│       ├── drop.go         #     · db drop
│       ├── migrate.go      #     · db migrate (up/down/status/reset)
│       ├── fix.go          #     · db fix
│       ├── schema.go       #     · db schema (dump/load)
│       ├── version.go      #     · version
│       ├── generate/       #     · generate subcommands (config, fizz, model, sql)
│       └── schema/         #     · schema subcommands (dump, load)
├── testdata/               # Migration files and model fixtures for tests
│   ├── migrations/         #   - Fizz and SQL migration examples
│   └── models/             #   - Generated model fixtures (a, b, ac, bc)
├── Makefile                # Build targets: install, build, test, lint, release
├── .goreleaser.yml         # Cross-platform release config (soda binary)
├── docker-compose.yml      # Local DB containers for test suite
├── database.yml            # Default database config used in tests
└── go.mod / go.sum

Entry points#

BinarySourcePurpose
sodasoda/main.gosoda/cmd.Execute()CLI tool for database lifecycle: create, drop, migrate, generate models/migrations, dump/load schema

The library itself (github.com/gobuffalo/pop/v6) has no main.go; it is consumed as a library. The doc.go file in the root provides the package-level godoc.

Package organization#

  • Internal packages:

    • internal/defaults — zero-value default helpers (e.g., defaults.String(val, fallback))
    • internal/randx — random string generation used in migration naming
  • Public packages (pkg/): Pop uses no pkg/ directory; all exported packages are at top-level sub-directories:

    • associations — Association metadata types and loaders (BelongsToAssociation, HasManyAssociation, HasOneAssociation, ManyToManyAssociation) used by the root package’s preload logic
    • columns — Struct-to-column reflection: Columns, Column, ReadableColumns, WriteableColumns; handles db: struct tags
    • slices — Typed scan-target slices (String, Int, Float64, UUID, Map) that implement sql.Scanner for multi-row result collection
    • logging — Single file defining log level constants
    • fix — Test fixture loading subsystem with Anko scripting support and auto-timestamp override
    • genny — Code generation templates and runners for config/migration/model scaffolding (used by soda generate)
  • Layering: The architecture is hub-and-spoke rather than layered. The root package is the hub — it imports all sub-packages (associations, columns, slices, logging, internal/*). The soda/cmd package imports the root pop package to call its public API. Sub-packages have no dependencies on each other, keeping the dependency graph acyclic and clean.

Build system#

  • Build tool: make (GNU Make) + GoReleaser for cross-platform distribution
  • Key targets:
    • make install — builds and installs soda binary with SQLite build tag
    • make buildgo build ./ (library check)
    • make testgo test -tags sqlite ./...
    • make lintgolangci-lint run
    • make release-dry-run / make release — Docker-based GoReleaser cross-compilation
  • Docker: docker-compose.yml for local test databases (PostgreSQL, MySQL, MariaDB, CockroachDB); no application Dockerfile. GoReleaser uses a goreleaser/goreleaser-cross Docker image for CGO cross-compilation of the SQLite-enabled soda binary.
  • Build tags: sqlite tag gates SQLite dialect code (files dialect_sqlite.go, dialect_sqlite_tag.go), since SQLite requires CGO. The tag is applied consistently via Makefile and GoReleaser config.

Notable structural decisions#

  • Flat root package as the library surface: All core types (Connection, Query, Model, Migrator, dialect) are in the root package. This simplifies import paths for library consumers — one import gets everything — but it creates a large, dense package (72 .go files) with broad internal coupling.
  • Dialect isolation by file, not sub-package: Each SQL dialect is a separate file (dialect_postgresql.go, dialect_mysql.go, etc.) in the root package rather than a sub-package, keeping them accessible to all root-level code without import cycles. The trade-off is that all dialect code is compiled into every binary even when only one dialect is used (except SQLite, which uses a build tag).
  • soda/ as a self-contained CLI subtree: The CLI lives under soda/ with its own main.go and cmd/ package, cleanly separated from the library. This mirrors patterns seen in projects like migrate or goose where the library and CLI are co-located but architecturally distinct.
  • genny/ for scaffolding templates: Rather than embedding templates in the binary or using a separate repository, code-generation templates live in genny/ as Go files with string constants. This avoids go:embed complexity (predating Go 1.16 embeds) while keeping templates version-controlled alongside the library.
  • fix/ package for fixture management: A dedicated package for loading test fixtures using the Anko scripting engine is unusual. It reflects Pop’s early Buffalo ecosystem integration, where fixture loading was a first-class concern.