Add db-migrations-flyway-liquibase: Flyway vs Liquibase migrations, rollbacks and baselines on Spring Boot 4.1

Companion code for the Flyway vs Liquibase article: checksum validation, out-of-order
and repeatable migrations, baselining an existing schema, Flyway Community's undo/diff/deploy
stubs, concurrent-startup locking for both tools, Liquibase changeset identity and rollback
(auto-generated vs explicit), a verified Liquibase 5.0.3 filename-caching defect, the new
OSS license service, the FSL license change, and running both tools against one database.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Q6XdRjtsp4862EM44T7i9a
This commit is contained in:
2026-09-15 07:08:57 +00:00
co-authored by Claude Sonnet 5
parent b02fbe1416
commit 3908331431
61 changed files with 3128 additions and 0 deletions
@@ -0,0 +1,68 @@
# 4. Out-of-order migrations
[← 3. Checksum validation](03-checksum-validation.md) · [Index](../README.md) · Next: [5. Repeatable migrations →](05-repeatable-migrations.md)
Two branches, both adding "the next migration": one ships `V3` and merges quickly, the other's
`V2` sits in a slow-to-review pull request and lands afterward. By the time `V2` merges, every
environment that deployed the first branch already has `V3` applied. What happens when `V2`
finally shows up?
[`FlywayOutOfOrderTest`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayOutOfOrderTest.java)
reproduces exactly this: `V1` and `V3` apply on a first startup, then `V2__add_note_length_check.sql`
is dropped in afterward, and a second startup runs against the same database.
**The common assumption — that Flyway just quietly skips the late-arriving lower version and
carries on — is wrong.** The whole application fails to start:
```
context failed to start: FlywayValidateException: Validate failed: Migrations have failed validation
Detected resolved migration not applied to database: 2.
To ignore this migration, set -ignoreMigrationPatterns='*:ignored'. To allow executing this migration, set -outOfOrder=true.
```
(from [`docs/output/03-flyway-out-of-order.txt`](output/03-flyway-out-of-order.txt); this test was
originally written expecting the silent-skip behaviour, and the real run corrected it)
Validation runs before migration, and by default (`spring.flyway.out-of-order=false`) a resolved
migration with a version lower than the highest already-applied one is treated as a validation
failure, not a no-op. Nothing gets applied and the context never comes up.
<svg viewBox="0 0 700 170" xmlns="http://www.w3.org/2000/svg" font-family="monospace" font-size="13">
<text x="20" y="25">history: V1 ✓, V3 ✓</text>
<rect x="20" y="40" width="60" height="30" fill="#dcfce7" stroke="#16a34a"/><text x="50" y="60" text-anchor="middle">V1</text>
<rect x="90" y="40" width="60" height="30" fill="#dcfce7" stroke="#16a34a"/><text x="120" y="60" text-anchor="middle">V3</text>
<rect x="160" y="40" width="60" height="30" fill="#fee2e2" stroke="#dc2626" stroke-dasharray="3 2"/><text x="190" y="60" text-anchor="middle">V2?</text>
<text x="260" y="60">out-of-order=false (default)</text>
<path d="M420 55 L470 55" stroke="#334155" stroke-width="2" marker-end="url(#a3)"/>
<rect x="470" y="35" width="200" height="40" rx="6" fill="#fee2e2" stroke="#dc2626"/>
<text x="570" y="60" text-anchor="middle">whole startup fails</text>
<text x="20" y="110">same history, out-of-order=true</text>
<path d="M270 105 L320 105" stroke="#334155" stroke-width="2" marker-end="url(#a3)"/>
<rect x="320" y="85" width="220" height="40" rx="6" fill="#dcfce7" stroke="#16a34a"/>
<text x="430" y="110" text-anchor="middle">V2 runs, slotted in after V3</text>
<text x="20" y="150" font-size="11" fill="#475569">history keeps its real apply order: V1, V3, V2 — not renumbered</text>
<defs><marker id="a3" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#334155"/></marker></defs>
</svg>
Setting `spring.flyway.out-of-order=true` is the fix the error message itself names, and a third
startup in the same test confirms what it actually does: `V2` runs, and the history table keeps it
in *real* application order — rank 3, version 2, sitting after rank 2's version 3 — Flyway does not
retroactively renumber or reorder anything:
```
installed_rank | version | description | success
---------------+---------+-------------------------------------------+--------
-1 | NULL | << Flyway Schema History table created >> | true
1 | 1 | init | true
2 | 3 | add note index | true
3 | 2 | add note length check | true
```
(from the same [transcript](output/03-flyway-out-of-order.txt))
## Going deeper
- **`outOfOrder` is a blunt, global switch** — turning it on doesn't just allow the one late migration you're expecting, it allows *any* lower-numbered migration to slot in from then on. Flyway's own docs call this out as reducing reproducibility, and `docs/output/03-flyway-out-of-order.txt`'s third-startup log line says so verbatim: `outOfOrder mode is active. Migration of schema may not be reproducible.`
- [`ignoreMigrationPatterns`](https://documentation.red-gate.com/flyway/flyway-cli-and-api/configuration/parameters/ignore-migration-patterns) (`rel="nofollow"`) — the error message's other suggested fix, for permanently ignoring a specific migration instead of relaxing ordering globally.
- A team that hits this regularly is usually missing a CI check that fails a PR when its migration's version number is lower than what's already merged to main — cheaper than relying on `outOfOrder` at all.