Part 18 of 18

Team Collaboration: Naming Conventions, Conflict Prevention, Git Workflow

A solo developer can treat Liquibase conventions as suggestions. A team of five cannot. When two developers both write “the next migration” on the same day, without a convention that prevents collision, you get duplicate IDs, merge conflicts on the master changelog, or — worst case — two changesets that silently overwrite each other in DATABASECHANGELOG.

This article is the operating agreement for teams using Liquibase: what conventions to establish, how to structure the git workflow, what the PR review checklist looks like, and what to do when conflicts happen despite the conventions.


The Uniqueness Contract

Liquibase identifies each changeset by three values stored in DATABASECHANGELOG:

id + author + filename = unique key

Two changesets with the same id and author in different files are distinct. Two changesets with the same id, author, and filename are considered the same changeset — and if one has already run, Liquibase will not re-run it (and will error if the content has changed).

The implication: your convention must make collisions impossible without coordination.


Changeset ID Convention

YYYYMMDD-NNN
- changeSet:
    id: "20260624-001"
    author: abhay

Two developers writing migrations on the same day get:

  • Developer A: 20260624-001, 20260624-002
  • Developer B: 20260624-001, 20260624-002

Collision. The date prefix alone is not enough.

Better: Timestamp + author initials

YYYYMMDD-INITIALS-NNN
# Developer A (Abhay Pratap)
- changeSet:
    id: "20260624-AP-001"
    author: abhay

# Developer B (Sara Chen)
- changeSet:
    id: "20260624-SC-001"
    author: sara

Now id + author is globally unique across the team, regardless of what time the changeset was written or which branch it was on.

Alternative: Unix timestamp (globally unique by definition)

- changeSet:
    id: "1719187200001"
    author: abhay

A Unix millisecond timestamp is unique unless two developers submit their changesets within the same millisecond. Not human-readable, but collision-proof.

The team choice: Pick one convention and document it in CONTRIBUTING.md. The convention matters less than the consistency. A team that uses YYYYMMDD-INITIALS-NNN universally will never have ID collisions. A team that uses inconsistent IDs will.


Author Convention

The author field identifies who wrote the changeset. It appears in DATABASECHANGELOG and in liquibase history output. Establish a consistent format:

FormatExampleNotes
First nameabhaySimple, readable, may collide in large teams
GitHub usernameabhay-psUnique per team, matches git blame
First.Lastabhay.pratapFormal, unambiguous
Emailabhay@company.comGlobally unique, verbose

Recommendation: GitHub username. It matches the author of the PR, is unique across the team, and makes DATABASECHANGELOG rows linkable to git history.


File Naming Convention

With includeAll (Article 7), file naming determines execution order. The team convention must guarantee:

  1. Files sort in the correct execution order alphabetically
  2. Files from different developers on the same day don’t collide

Recommended: YYYYMMDD-INITIALS-NNN-description.yaml

20260624-AP-001-add-phone-to-users.yaml
20260624-SC-001-create-coupons-table.yaml
20260625-AP-001-add-index-on-phone.yaml

Developer A and B can both add files on 20260624 with no collision. Files sort chronologically across the team. The description makes git log -- db/changelog/migrations/ readable without opening files.


The Master Changelog Rule

With includeAll in the master changelog, the master changelog file is never edited by developers. It is established once at project setup and never touched again:

# db.changelog-master.yaml — owned by infrastructure, never touched by developers
databaseChangeLog:
  - includeAll:
      path: db/changelog/migrations/
      relativeToChangelogFile: false

Every developer adds only new migration files. The master changelog is immutable from the team’s perspective. This eliminates an entire category of merge conflicts — two developers cannot conflict on a file neither of them touches.


Git Workflow

Feature branch with DB migration

main
│
├─ feature/add-coupons (Sara Chen)
│   ├─ src/main/java/.../CouponService.java
│   └─ db/changelog/migrations/2026/06/20260624-SC-001-create-coupons-table.yaml
│
└─ feature/user-phone (Abhay Pratap)
    ├─ src/main/java/.../UserService.java
    └─ db/changelog/migrations/2026/06/20260624-AP-001-add-phone-to-users.yaml

Each feature branch adds one or more migration files alongside application code. The migration files are isolated by the filename convention — there is no shared file to conflict on.

When both branches merge to main:

main
└─ db/changelog/migrations/2026/06/
    ├─ 20260624-AP-001-add-phone-to-users.yaml
    └─ 20260624-SC-001-create-coupons-table.yaml

Both files exist, in alphabetical order (AP before SC). Liquibase will run AP’s migration before SC’s — which is correct if AP’s migration doesn’t depend on SC’s. If there is a dependency, the later developer must use a later date prefix.

When merge order determines execution order

If Sara’s create-coupons-table depends on Abhay’s add-phone-to-users (unlikely in this example, but illustrative), Sara must date her file after Abhay’s is known to be in main:

20260624-AP-001-add-phone-to-users.yaml  ← Abhay, merged first
20260625-SC-001-create-coupons-table.yaml  ← Sara, uses next-day date to guarantee ordering

The convention: if your migration depends on another migration that is currently in review, use a later date to guarantee it sorts after the dependency.


Detecting and Resolving Merge Conflicts

Scenario 1: ID collision

Both developers independently chose the same ID.

# Abhay's changeset
id: "20260624-001"
author: abhay

# Sara's changeset (in the same file — shouldn't happen with separate files)
id: "20260624-001"
author: sara

With separate files and the author-initial convention, this collision cannot happen. If you are using a simpler convention and it does happen:

  1. Change one of the IDs (the one not yet applied to any environment)
  2. Update the filename to match the new convention
  3. Never change the ID of a changeset already applied to any database

Scenario 2: Two developers add the same table

Sara and Abhay both add a create-coupons-table changeset in different branches. After merge, the second one to run fails with “table already exists.”

Resolution:

  1. The second changeset developer adds a tableExists precondition with onFail: MARK_RAN
  2. Or one of the duplicate changesets is deleted (the one that never ran on any environment)

This is why regular communication about planned schema changes matters — post in a #db-migrations Slack channel before writing a changeset. The collision is easy to avoid, hard to fix after the fact.

Scenario 3: Git conflict on a shared migration file

This happens when two developers both edited the same YAML migration file (not just added new files). With the one-file-per-migration convention, this should be impossible. If it happens, it means the team violated the convention — someone edited an existing file instead of creating a new one.

Resolution: identify which edits are legitimate and which violated the convention. The legitimate edit goes in a new changeset file. The violated edit is reverted.


PR Review Checklist for Database Migrations

Every PR that includes a migration file should be reviewed against this checklist. Add it to your PR template:

## Database Migration Checklist

### Changeset metadata
- [ ] ID follows the team convention (`YYYYMMDD-INITIALS-NNN`)
- [ ] Author is the GitHub username of the PR author
- [ ] Comment clearly describes what the changeset does and why

### Content
- [ ] One logical change per changeset (table and index in separate changesets)
- [ ] Rollback block is present and correct for all non-auto-rollback change types
- [ ] `dropTable`, `dropColumn`, `modifyDataType`, `insert`, `update`, `sql` all have explicit rollback
- [ ] MySQL-specific features use `sql` changeset or `modifySql` (not standard change types that silently produce wrong DDL)
- [ ] `utf8mb4` charset specified in `CREATE TABLE` (via `modifySql` or `sql`)

### Environment safety
- [ ] Seed data changesets have `context: dev` (never runs in prod)
- [ ] Production data migrations are marked `context: prod` if prod-only
- [ ] MySQL-specific changesets have `dbms: mysql` precondition (skips H2 in tests)
- [ ] Large table changes considered: is Percona extension needed? Is Online DDL available?

### File and ordering
- [ ] File is in the correct `migrations/YYYY/MM/` directory
- [ ] Filename follows `YYYYMMDD-INITIALS-NNN-description.yaml` convention
- [ ] File sort order is correct relative to other pending changesets
- [ ] No edits to existing migration files that have been applied to any environment

Team Rules in CONTRIBUTING.md

Document the conventions where all developers will find them:

# Database Migrations

## Adding a migration

1. Create a new file in `src/main/resources/db/changelog/migrations/YYYY/MM/`
2. Filename: `YYYYMMDD-INITIALS-NNN-description.yaml`
   - `YYYYMMDD`: today's date
   - `INITIALS`: your initials (2-3 uppercase letters, consistent)
   - `NNN`: sequential number for multiple files on the same day (001, 002, ...)
   - `description`: kebab-case description of the change
3. Changeset ID: `YYYYMMDD-INITIALS-NNN` (matches filename prefix)
4. Author: your GitHub username

## Rules

- **Never edit a migration file once it has been applied to any environment** (dev, staging, prod)
- **Never edit the master changelog** (`db.changelog-master.yaml`) — it is managed by `includeAll`
- **Every changeset needs a rollback block** — unless the change type has auto-rollback AND you are not using a `sql` or `modifyDataType` changeset
- **Seed data uses `context: dev`** — never run test data on production
- **Post in #db-migrations before writing a migration** that creates a new table or alters a shared table — coordinate to avoid collisions

## Naming table and constraint objects

| Object | Convention | Example |
|---|---|---|
| Table | `snake_case` | `order_items` |
| Column | `snake_case` | `created_at` |
| Primary key | (default) | MySQL generates `PRIMARY` |
| Foreign key | `fk_{table}_{column}` | `fk_orders_user_id` |
| Index | `idx_{table}_{columns}` | `idx_orders_user_status` |
| Unique constraint | `uq_{table}_{column}` | `uq_users_email` |
| Trigger | `trg_{table}_{timing}_{event}` | `trg_users_after_update` |
| View | `v_{name}` | `v_user_order_summary` |
| Procedure | `sp_{name}` | `sp_order_analytics` |

Common Mistakes

Using sequential integers as changeset IDs: IDs like 1, 2, 3 guarantee collisions in any team. Two developers on separate branches both write “changeset 47.” Timestamps or date+initial prefixes make this impossible.

Editing existing migration files: The instinct to “fix a typo” in a migration file is understandable. If the file has been applied to any environment, the edit causes a checksum mismatch. Always create a new changeset for corrections — never edit applied ones.

Not communicating before writing large migrations: Table creations, schema restructures, and migrations that touch high-traffic tables should be announced in the team channel before development starts. This catches intent conflicts (two developers adding the same table) and performance concerns (DBA review for large table changes) before time is invested.


Best Practices

  • YYYYMMDD-INITIALS-NNN-description.yaml as the universal filename and ID prefix — collision-proof, human-readable, sort-correct
  • GitHub username as author — unique, links to git history, appears meaningfully in liquibase history
  • includeAll in the master changelog — removes the master changelog as a conflict surface entirely
  • PR checklist for every migration — the checklist is the code review; without it, rollback blocks get skipped and conventions drift
  • #db-migrations channel — announce before writing large migrations; five minutes of coordination prevents hours of conflict resolution
  • Document conventions in CONTRIBUTING.md — conventions only work if everyone knows them; the document is the enforcement mechanism

What You’ve Learned

  • Changeset uniqueness is id + author + filename — your naming convention must make all three unique without coordination
  • YYYYMMDD-INITIALS-NNN for IDs and filenames makes individual developer output non-overlapping
  • GitHub username as author makes DATABASECHANGELOG rows linkable to git history
  • includeAll in the master changelog eliminates master changelog merge conflicts — no developer ever touches it
  • Feature branches add new files; they never edit existing migration files; they never touch the master changelog
  • The PR checklist enforces rollback, charset, context, and naming conventions at review time

Part 3 complete. You now have the full advanced toolkit: stored objects, MySQL-specific production patterns, diff and reverse engineering, production rollback operations, zero-downtime deployments, and team collaboration conventions.

Next: Article 19 — CI/CD Integration: GitHub Actions Pipeline for Database Deployments — the first article of Part 4, wiring everything together into an automated pipeline.