Java.DBMigrationTools.How would you structure your changelog/migration folder hierarchy?

Here are folder hierarchies I’d recommend (and why). I’ll give you two “battle-tested” layouts: one for Flyway (SQL-first) and one for Liquibase (master + includes). Both are designed to minimize merge conflicts, support multi-env, and keep things discoverable.

Flyway folder hierarchy (recommended for most teams)

db/
  migration/
    V20260120_1015__create_users_table.sql
    V20260120_1040__add_email_verified_column.sql
    V20260120_1200__backfill_email_verified.sql
    V20260121_0900__idx_users_email_verified.sql
  callbacks/
    beforeMigrate.sql
    afterMigrate.sql
    afterEachMigrate.sql
  repeatable/
    R__views.sql
    R__functions.sql
    R__permissions.sql

Rules

  • One change = one migration (schema, data backfill, index).
  • Use timestamp versions to avoid collisions: VYYYYMMDD_HHMMSS__desc.sql.
  • Put “non-versioned but re-applied” things in repeatables:
    • views, functions, grants (stuff you want to converge to the latest definition).
  • Keep callbacks separate and tiny.

When you have multiple schemas

db/
  migration/
    public/
      V...
    billing/
      V...

When you have multiple DBs (multi-tenant but separate databases)

  • Keep the same scripts; orchestration decides which DBs to run on.
  • Don’t duplicate migrations per tenant.

Liquibase hierarchy (master changelog + per-change files)

db/
  changelog/
    db.changelog-master.yaml
    changes/
      2026/
        01/
          2026-01-20_001_create_users_table.yaml
          2026-01-20_002_add_email_verified.yaml
          2026-01-20_003_backfill_email_verified.yaml
          2026-01-21_001_idx_users_email_verified.yaml
    repeatable/
      views/
        users_view.yaml
      functions/
        normalize_phone.yaml
    testdata/
      2026-01-20_seed_minimal.yaml

Master file is boring on purpose

  • It only includes other files (ideally whole directories).
  • You want people creating new files under changes/..., not editing one big file.

Changeset conventions

  • Unique id: 2026-01-20-001-create-users
  • author: team/service or username
  • Prefer logicalFilePath stability if you move files.

Multi-service monorepo layout (common in real orgs)

services/
  payments/
    db/
      migration/   (or changelog/)
  customers/
    db/
      migration/
shared/
  db/
    repeatable/    (optional shared views/functions only if truly shared)

Rule: migrations are owned by the service that owns the schema. Shared DB objects are usually a smell unless you’ve intentionally centralized schema ownership.

Environment-specific migrations (handle carefully)

Avoid “prod-only migrations” if you can. If you must:

  • Liquibase: use contexts/labels, but don’t hide required schema behind them.
  • Flyway: separate locations, e.g. db/migration + db/migration-prod, and keep it rare.

Prefer: same migrations everywhere; differences should be configuration, not schema.


Naming standards (what prevents chaos)

  • Prefix ordering: YYYY-MM-DD_###_...
  • Short, verb-first description: add_, create_, drop_, backfill_, idx_
  • Keep the file name close to what it does:
    • ...__idx_users_email_verified.sql not ...__misc.sql

My “interview answer” (tight)

“I structure migrations so each change is a separate file, ordered deterministically by timestamp to avoid collisions. For Flyway I keep versioned migrations in db/migration, repeatables for views/functions/grants, and optional callbacks. For Liquibase I keep a minimal master changelog that includes a changes/YYYY/MM folder, so merge conflicts become adding files, not editing shared ones.”

This entry was posted in Без рубрики. Bookmark the permalink.