NashTech Blog

Database Deployment Strategy with Liquibase – A Developer-Friendly Approach

Table of Contents

Introduction

Managing database changes in an evolving application can be challenging — especially when your schema and database objects keep growing over time. Without a well-defined strategy, developers often spend more time fixing deployment issues than writing actual business logic.

In this post, I’ll share our Liquibase-based deployment strategy designed to help developers focus on coding while ensuring database changes are consistent, versioned, and easy to deploy.

The Challenge

With Liquibase, every schema change is defined in a changeset.
Over time, teams accumulate dozens or even hundreds of DDL files, often with no clear naming or organization. This makes it difficult to know:

  • Which file is the latest version of a table.
  • Whether changes have already been deployed.
  • How to maintain dependency order (tables before triggers, etc.).

In our case, the original folder structure looked like this:

Before (unstructured)

db-schema/
├── TICKET-101/
│   ├── create_employees.sql              # creates employees table
│   ├── alter_projects.sql                # adds columns to projects
│   └── fix_constraints.sql               # quick constraint fixes
├── TICKET-102/
│   ├── create_departments.sql            # creates departments table
│   ├── update_employees.sql              # modifies employees from TICKET-101
│   └── projects_more_changes.sql         # more changes to projects from TICKET-101
└── TICKET-103/
    ├── add_index_to_projects.sql         # indexes on projects (depends on prior alters)
    └── employees_hotfix.sql              # emergency tweak to employees (again from TICKET-101)

    Why this is problematic

    • Cross‑ticket edits with vague names: update_employees.sql, projects_more_changes.sql, employees_hotfix.sql don’t say what changed or in what order they must run.
    • No version indicators: You can’t tell which change is “latest” for employees or projects.
    • Mixed concerns in one ticket: DDL for multiple objects sits together, obscuring dependencies.
    • Deployment ordering risk: Applying TICKET-103 before TICKET-102 could fail if required columns/indexes don’t exist yet.

      Our Deployment Strategy

      We separate database definitions into two main categories:

      1. Tables

      Includes:

      • Table creation & modifications
      • Column additions, changes, deletions
      • Primary keys, foreign keys, and constraints
      • Indexes & unique constraints

      Principles:

      • Idempotent: Safe to run multiple times (e.g., using IF NOT EXISTS).
      • Versioned: Use incremental numbers after release.

      Example naming:

      employees.001.sql   -- Initial table
      employees.002.sql   -- Add department_id column
      projects.001.sql    -- Initial table

      2. Database Objects

      Includes:

      • Functions & stored procedures
      • Trigger functions & triggers
      • Views
      • Other programmable objects

      Principles:

      • Always use CREATE OR REPLACE where possible.
      • Use runOnChange="true" so changes are always applied.

      Example naming:

      fn_calculate_employee_tenure.sql
      trigger_employee_audit_log.sql
      vw_employee_summary.sql
      sp_monthly_report.sql

      File Naming Conventions

      Tables
      table_name.XXX.sql
      • .001.sql: Original definition (modifiable until release)
      • .002.sql: First post-release change
      • .003.sql: Next change
      Database Objects
      objectType_objectName.sql

      Prefixes:

      • fn – Function
      • vw – View
      • trigger – Trigger
      • sp – Stored Procedure

      Liquibase Changelog Structure

      Master changelog (domain_name.changelog.yml):
      databaseChangeLog:
        - include:
            file: tables.changelog.yml
            relativeToChangelogFile: true
        - include:
            file: database-objects.changelog.yml
            relativeToChangelogFile: true
      
      Tables changelog (tables.changelog.yml):
      databaseChangeLog:
        - changeSet:
            id: <ticket ID>-<brief summary>
            author: dev
            runOnChange: false
            changes:
              - sqlFile:
                  path: tables/employees.001.sql
                  relativeToChangelogFile: true
      

      Migration Best Practices

      1. Separate concerns: Tables vs objects.
      2. Respect dependency order: Tables first, dependent objects after.
      3. Group related changes: e.g., all audit triggers in one changeset.
      4. Rollback support: Always provide rollback scripts when possible.
      5. Test before release: Run in dev/staging first.
      6. Document complex logic: Use comments for maintainability.

      Folder Structure

      db-schema/
      ├── domain_name.changelog.yml
      ├── tables.changelog.yml
      ├── database-objects.changelog.yml
      ├── tables/
      │   ├── latest/
      │   │   ├── employees.sql
      │   │   └── projects.sql
      │   ├── employees.001.sql
      │   ├── employees.002.sql
      │   ├── projects.001.sql
      └── database-objects/
          ├── fn_calculate_employee_tenure.sql
          ├── trigger_employee_audit_log.sql
          ├── vw_employee_summary.sql
          └── sp_monthly_report.sql
      

      Key Rules Recap

      • One table per file.
      • .001 files are modifiable until release.
      • Post-release changes require .002, .003
      • Use lowercase for field types, uppercase for SQL keywords.
      • All changes go through Liquibase — no direct DB edits.

      Conclusion

      A clear Liquibase deployment strategy saves hours of developer time, reduces production issues, and ensures database changes are traceable and repeatable.

      By following structured naming, changelog organization, and best practices, you’ll keep deployments smooth while letting developers focus on writing features — not firefighting migration issues.

      Picture of San Nguyen

      San Nguyen

      Leave a Comment

      Your email address will not be published. Required fields are marked *

      Suggested Article

      Scroll to Top