Flyway và Liquibase: Tự động hóa di chuyển cơ sở dữ liệu trong các dự án thực tế

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Tự động hóa quản lý thay đổi lược đồ cơ sở dữ liệu

Quản lý các thay đổi lược đồ cơ sở dữ liệu giữa các môi trường phát triển, thử nghiệm và sản xuất có thể giống như đi trên dây. Một cột bị bỏ sót, một chỉ mục bị quên, hoặc một tập lệnh được áp dụng không chính xác có thể dễ dàng dẫn đến thời gian ngừng hoạt động, hỏng dữ liệu hoặc các lỗi gây khó chịu.

May mắn thay, các công cụ di chuyển cơ sở dữ liệu như Flyway và Liquibase tự động hóa quá trình quan trọng này, đảm bảo tính nhất quán và độ tin cậy. Nếu bạn đã từng tự chạy các tập lệnh SQL, thầm hy vọng rằng mình không quên bước nào, thì bài viết này chắc chắn dành cho bạn.

So sánh các phương pháp: Các lựa chọn quản lý lược đồ cơ sở dữ liệu

Trước khi chúng ta đi sâu vào Flyway và Liquibase, hãy cùng khám phá các phương pháp phổ biến để quản lý lược đồ cơ sở dữ liệu. Hầu hết các đội thường thuộc một trong các trường hợp sau:

Các tập lệnh SQL thủ công

Đối với nhiều dự án, các tập lệnh SQL thủ công thường là phương pháp đầu tiên mà các đội sử dụng. Các nhà phát triển tạo các tệp .sql riêng lẻ cho mỗi thay đổi lược đồ, như V1__create_users_table.sql hoặc V2__add_email_column.sql. Sau đó, các tập lệnh này được thực thi thủ công trên các cơ sở dữ liệu thích hợp.

  • Ưu điểm: Cung cấp toàn quyền kiểm soát, và các chuyên gia SQL hầu như không cần học thêm gì.
  • Nhược điểm: Rất dễ xảy ra lỗi do con người (ví dụ: chạy sai tập lệnh hoặc quên hoàn toàn), gây khó khăn trong việc theo dõi các lần di chuyển đã áp dụng, thiếu cơ chế khôi phục (rollback) tích hợp sẵn, và gây thách thức cho các đội phát triển phân tán.

Di chuyển dựa trên ORM

Nhiều Object-Relational Mapper (ORM), như Django ORM, Hibernate, hoặc Entity Framework, được trang bị khả năng di chuyển riêng. Các ORM này thường tự động tạo các tập lệnh SQL dựa trên những thay đổi được phát hiện trong các mô hình hoặc thực thể của ứng dụng.

  • Ưu điểm: Liên kết chặt chẽ với mã ứng dụng của bạn, có tính năng tạo tập lệnh tự động và đơn giản hóa quy trình phát triển tổng thể.
  • Nhược điểm: Chúng cũng có thể kém linh hoạt hơn đối với các tính năng phức tạp, đặc thù của cơ sở dữ liệu như stored procedure (thủ tục lưu trữ) hoặc trigger (bộ kích hoạt). Đôi khi, SQL được tạo ra không tối ưu; ví dụ, một ORM có thể tạo các câu lệnh ALTER TABLE riêng lẻ cho mỗi lần thêm cột thay vì nhóm chúng lại để tăng hiệu quả. Hơn nữa, việc phụ thuộc quá nhiều vào các lần di chuyển của ORM có thể khiến bạn bị ràng buộc vào một ORM cụ thể, gây khó khăn hơn khi chuyển đổi công nghệ sau này.

Các công cụ di chuyển cơ sở dữ liệu chuyên dụng (Flyway & Liquibase)

Ngược lại, các công cụ chuyên dụng như Flyway và Liquibase tập trung hoàn toàn vào việc quản lý lược đồ cơ sở dữ liệu. Chúng cung cấp các giải pháp không phụ thuộc vào ngôn ngữ, tương thích với hầu hết mọi cơ sở dữ liệu và ngăn xếp ứng dụng. Các công cụ này theo dõi các thay đổi đã áp dụng, quản lý việc tạo phiên bản và cung cấp các cơ chế mạnh mẽ để áp dụng—và đôi khi khôi phục—các lần di chuyển.

  • Ưu điểm: Không phụ thuộc vào cơ sở dữ liệu, cung cấp kiểm soát phiên bản mạnh mẽ, hỗ trợ phát triển cộng tác tốt, xuất sắc trong việc xử lý các thay đổi lược đồ phức tạp và duy trì lịch sử thay đổi rõ ràng, có thể kiểm tra được.
  • Nhược điểm: Yêu cầu tích hợp thêm một công cụ vào quy trình xây dựng của bạn và đòi hỏi một chút thời gian học hỏi để hiểu các quy ước cụ thể của chúng.

Ưu và nhược điểm: Flyway so với Liquibase

Cả Flyway và Liquibase đều giải quyết cùng một vấn đề cốt lõi, nhưng chúng tiếp cận vấn đề với những triết lý và cơ chế cơ bản khác nhau.

Flyway

Flyway hoạt động theo nguyên tắc "quy ước hơn là cấu hình," ưu tiên các tập lệnh SQL đơn giản, có phiên bản.

  • Ưu điểm:
    • Đơn giản: Nếu bạn quen thuộc với SQL, Flyway cực kỳ dễ học. Bạn chỉ cần viết các tệp SQL thuần túy, thêm tiền tố là số phiên bản (ví dụ: V1.0.1__create_users_table.sql), và Flyway sẽ quản lý quá trình áp dụng từ đó.
    • Dễ dự đoán: Thứ tự di chuyển được xác định nghiêm ngặt bởi tên tệp, làm cho toàn bộ luồng đặc biệt rõ ràng và dễ theo dõi.
    • Nhẹ: Yêu cầu tối thiểu tài nguyên và thường được tích hợp trực tiếp vào quá trình khởi động ứng dụng của bạn.
    • Tập trung vào SQL thuần túy: Khuyến khích viết SQL dành riêng cho cơ sở dữ liệu, cho phép bạn tận dụng các tính năng và tối ưu hóa tiên tiến nhất của cơ sở dữ liệu đã chọn.
    • Khôi phục cho DML: Mặc dù Flyway không cung cấp tính năng khôi phục DDL tự động, bạn có thể viết các tập lệnh khôi phục rõ ràng cho Data Manipulation Language (DML) nếu cần. Ngoài ra, bạn có thể quản lý các thay đổi DDL bằng chiến lược "chỉ tiến tới" (forward-only).
  • Nhược điểm:
    • Tập trung vào SQL: Nếu đội của bạn không thạo SQL, hoặc nếu bạn thích một lớp trừu tượng cao hơn, bản chất tập trung vào SQL của nó là một trở ngại đáng kể.
    • Không tự động khôi phục DDL: Đối với các thay đổi lược đồ (DDL), Flyway không tự động tạo tập lệnh khôi phục. Bạn phải quản lý việc khôi phục thủ công, bằng cách áp dụng một lần di chuyển revert tùy chỉnh hoặc khôi phục từ bản sao lưu.
    • Trừu tượng hóa hạn chế: Nếu bạn cần hỗ trợ nhiều loại cơ sở dữ liệu với cùng một logic di chuyển cốt lõi, bạn có thể phải viết các tập lệnh SQL riêng biệt được điều chỉnh cho từng nhà cung cấp cơ sở dữ liệu.

Liquibase

Liquibase áp dụng một cách tiếp cận trừu tượng hơn, hướng dữ liệu, cho phép bạn định nghĩa các lần di chuyển bằng nhiều định dạng khác nhau như XML, YAML, JSON hoặc thậm chí là SQL thuần túy.

  • Ưu điểm:
    • Trừu tượng hóa cơ sở dữ liệu: Định dạng changeset cho phép bạn định nghĩa các lần di chuyển một lần, và Liquibase sẽ dịch chúng thành SQL chính xác cho các loại cơ sở dữ liệu khác nhau. Đây là một lợi thế lớn, đặc biệt khi nhắm mục tiêu đến nhiều nhà cung cấp cơ sở dữ liệu.
    • Nhiều định dạng: Bạn có thể chọn giữa XML, YAML, JSON, hoặc SQL thuần túy cho các changelog của mình, phù hợp với sở thích đa dạng của đội và yêu cầu dự án.
    • Khôi phục tự động: Liquibase thường có thể tạo SQL khôi phục cho nhiều loại thay đổi phổ biến, cung cấp một mạng lưới an toàn mạnh mẽ và tự động.
    • Ngữ cảnh và nhãn: Cho phép bạn gắn thẻ các lần di chuyển để áp dụng chúng có điều kiện (ví dụ: chỉ áp dụng "dữ liệu thử nghiệm" trong môi trường phát triển, hoặc "hotfix" chỉ cho môi trường sản xuất).
    • Khả năng tái cấu trúc: Cung cấp các loại thay đổi tích hợp sẵn, cụ thể cho các hoạt động tái cấu trúc cơ sở dữ liệu phổ biến, chẳng hạn như renameColumn hoặc addForeignKeyConstraint.
  • Nhược điểm:
    • Phức tạp: Các định dạng changelog XML, YAML hoặc JSON thường dài dòng và có đường cong học tập dốc hơn đáng kể so với các tệp SQL đơn giản. Việc gỡ lỗi các vấn đề trong các định dạng khai báo này cũng có xu hướng phức tạp hơn.
    • SQL được tạo: Mặc dù khả năng trừu tượng hóa của nó rất mạnh mẽ, SQL được Liquibase tạo ra có thể không phải lúc nào cũng hiệu quả tối ưu hoặc chính xác như những gì một DBA có kinh nghiệm sẽ tự viết.
    • Chi phí phát sinh: Nó có thể cảm thấy nặng nề hơn Flyway, đặc biệt trong các dự án rất nhỏ, do bộ tính năng phong phú hơn và cấu hình dài dòng hơn.

Thiết lập đề xuất: Chọn công cụ và tích hợp nó

Việc lựa chọn giữa Flyway và Liquibase thường phụ thuộc vào sự quen thuộc của đội bạn với SQL, liệu bạn có cần hỗ trợ nhiều cơ sở dữ liệu hay không, và sở thích của bạn đối với SQL tường minh so với một lớp trừu tượng cao hơn.

  • Chọn Flyway nếu:
    • Dự án của bạn chủ yếu sử dụng một loại cơ sở dữ liệu duy nhất.
    • Đội của bạn thoải mái với việc viết và xem xét SQL thuần.
    • Bạn ưu tiên sự đơn giản và minh bạch trong các tập lệnh di chuyển của mình.
    • Bạn thoải mái với việc xử lý khôi phục thủ công hoặc áp dụng chiến lược di chuyển "chỉ tiến tới" cho các thay đổi DDL.
  • Chọn Liquibase nếu:
    • Bạn cần hỗ trợ nhiều loại cơ sở dữ liệu với một codebase duy nhất.
    • Bạn coi trọng khả năng khôi phục tự động, mạnh mẽ.
    • Đội của bạn ưa thích cách tiếp cận khai báo hơn đối với các thay đổi lược đồ, đặc biệt nếu một số nhà phát triển ít kinh nghiệm với các phương ngữ SQL cụ thể.
    • Bạn yêu cầu các tính năng nâng cao như ngữ cảnh, nhãn hoặc các loại tái cấu trúc phức tạp.

Sau khi bạn đã đưa ra lựa chọn, bước hợp lý tiếp theo là tích hợp. Hầu hết các ứng dụng hiện đại đều nhúng các công cụ này trực tiếp vào quá trình xây dựng của chúng (ví dụ: Maven hoặc Gradle cho các dự án Java) hoặc thực thi chúng như một bước dòng lệnh chuyên dụng trong pipeline CI/CD của chúng. Ngay cả đối với các ứng dụng không được xây dựng bằng Java, cả hai công cụ đều cung cấp các máy khách dòng lệnh mà bạn có thể gọi từ hầu hết mọi ngôn ngữ kịch bản.

Hướng dẫn triển khai: Thực hành với các lần di chuyển

Hãy cùng xem qua một số ví dụ cơ bản, thực tế cho cả hai công cụ. Tôi sẽ sử dụng Java với Maven để minh họa, vì đây là điểm tích hợp phổ biến, nhưng hãy luôn nhớ rằng các khái niệm cốt lõi vẫn áp dụng bất kể ngăn xếp ứng dụng của bạn là gì.

Ví dụ Flyway: Di chuyển SQL đơn giản

1. Thêm Flyway vào dự án của bạn (Maven pom.xml):

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>9.16.0</version> <!-- Sử dụng phiên bản ổn định mới nhất -->
</dependency>

2. Cấu hình Flyway (ví dụ: application.properties hoặc tệp cấu hình tương tự):

Flyway có thể được cấu hình theo chương trình hoặc thông qua các tệp thuộc tính. Nếu bạn đang sử dụng Spring Boot, nó thường tự động cấu hình với ít nỗ lực. Dưới đây là một ví dụ thiết lập cơ bản theo chương trình:

// Ví dụ: Cấu hình theo chương trình trong một ứng dụng Java
import org.flywaydb.core.Flyway;
import javax.sql.DataSource;

public class DatabaseMigrator {
    public static void migrate(DataSource dataSource) {
        Flyway flyway = Flyway.configure()
            .dataSource(dataSource)
            .locations("classpath:db/migration") // Chỉ định nơi các tập स्क्रिप्ट di chuyển SQL của bạn được đặt
            .load();
        flyway.migrate();
    }
}

3. Tạo tập lệnh di chuyển đầu tiên của bạn (src/main/resources/db/migration/V1__Create_initial_tables.sql):

Flyway yêu cầu các tập lệnh di chuyển phải tuân theo quy ước đặt tên nghiêm ngặt: V<VERSION>__<DESCRIPTION>.sql. Phần phiên bản có thể bao gồm các số và dấu chấm, chẳng hạn như 1, 1.1, hoặc 2.0.1.

-- V1__Tạo_các_bảng_ban_đầu.sql
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    description TEXT
);

4. Tạo tập lệnh di chuyển thứ hai (src/main/resources/db/migration/V2__Add_products_category.sql):

-- V2__Thêm_danh_mục_sản_phẩm.sql
ALTER TABLE products
ADD COLUMN category VARCHAR(50) DEFAULT 'Uncategorized';

UPDATE products
SET category = 'Electronics'
WHERE id = 1; -- Ví dụ: Cập nhật danh mục cho một sản phẩm cụ thể

5. Chạy quá trình di chuyển:

Khi ứng dụng của bạn khởi động và DatabaseMigrator.migrate(dataSource) được gọi, hoặc khi bạn thực thi mvn flyway:migrate (nếu sử dụng plugin Maven), Flyway sẽ thực hiện các bước sau:

  • Nó sẽ kiểm tra bảng flyway_schema_history của nó (hoặc một tên tùy chỉnh bạn cấu hình) trong cơ sở dữ liệu của bạn.
  • Nó sẽ xác định bất kỳ lần di chuyển đang chờ xử lý nào (ví dụ: V1, V2) chưa được áp dụng.
  • Nó sẽ thực thi các lần di chuyển này theo trình tự, theo đúng thứ tự phiên bản.
  • Cuối cùng, nó sẽ ghi lại việc thực thi thành công của chúng vào bảng lịch sử.
# Ví dụ sử dụng công cụ dòng lệnh Flyway hoặc plugin Maven
# Nếu sử dụng Maven:
mvn flyway:migrate

# Nếu sử dụng máy khách dòng lệnh, sau khi cấu hình
flyway migrate

Sau khi chạy các lệnh này, bảng products của bạn giờ đây sẽ có một cột category mới, và sản phẩm có id=1 sẽ được cập nhật danh mục rõ ràng. Flyway tự động tạo một bảng flyway_schema_history để theo dõi cẩn thận tất cả các lần di chuyển đã áp dụng và trạng thái của chúng.

Ví dụ Liquibase: Các Changeset trừu tượng hóa

1. Thêm Liquibase vào dự án của bạn (Maven pom.xml):

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>4.27.0</version> <!-- Sử dụng phiên bản ổn định mới nhất -->
</dependency>

2. Tạo tệp changelog chính (src/main/resources/db/changelog/db.changelog-master.yaml):

Tệp chính này đóng vai trò là điểm vào chính cho Liquibase, điều phối và bao gồm các tệp changelog riêng lẻ khác.

# db.changelog-master.yaml
databaseChangeLog:
  - include:
      file: db/changelog/001-initial-schema.yaml
  - include:
      file: db/changelog/002-add-product-category.yaml

3. Tạo changelog đầu tiên của bạn (src/main/resources/db/changelog/001-initial-schema.yaml):

# 001-initial-schema.yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: ten_cua_ban
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: INT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: username
                  type: VARCHAR(50)
                  constraints:
                    nullable: false
                    unique: true
              - column:
                  name: email
                  type: VARCHAR(100)
                  constraints:
                    nullable: false
              - column:
                  name: created_at
                  type: TIMESTAMP
                  defaultValueComputed: CURRENT_TIMESTAMP
        - createTable:
            tableName: products
            columns:
              - column:
                  name: id
                  type: INT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: name
                  type: VARCHAR(100)
                  constraints:
                    nullable: false
              - column:
                  name: price
                  type: DECIMAL(10, 2)
                  constraints:
                    nullable: false
              - column:
                  name: description
                  type: TEXT

4. Tạo changelog thứ hai (src/main/resources/db/changelog/002-add-product-category.yaml):

# 002-add-product-category.yaml
databaseChangeLog:
  - changeSet:
      id: 2
      author: ten_cua_ban
      changes:
        - addColumn:
            tableName: products
            columns:
              - column:
                  name: category
                  type: VARCHAR(50)
                  defaultValue: 'Uncategorized'
        - update:
            tableName: products
            set:
              category: 'Electronics'
            where: "id = 1"

5. Chạy quá trình di chuyển:

Tương tự như Flyway, Liquibase có thể được chạy theo chương trình, thông qua plugin Maven/Gradle, hoặc trực tiếp qua máy khách dòng lệnh của nó.

// Ví dụ: Cấu hình theo chương trình trong một ứng dụng Java
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor;
import javax.sql.DataSource;
import java.sql.Connection;

public class DatabaseMigrator {
    public static void migrate(DataSource dataSource) throws Exception {
        try (Connection connection = dataSource.getConnection()) {
            Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
            Liquibase liquibase = new Liquibase("db/changelog/db.changelog-master.yaml", new ClassLoaderResourceAccessor(), database);
            liquibase.update(""); // Một ngữ cảnh trống có nghĩa là tất cả các changeset sẽ được áp dụng
        }
    }
}
# Ví dụ sử dụng công cụ dòng lệnh Liquibase hoặc plugin Maven
# Nếu sử dụng Maven:
mvn liquibase:update

# Nếu sử dụng máy khách dòng lệnh, sau khi cấu hình
liquibase update

Khi Liquibase chạy, nó tạo hai bảng thiết yếu trong cơ sở dữ liệu của bạn: DATABASECHANGELOG (hoạt động tương tự như bảng lịch sử của Flyway) và DATABASECHANGELOGLOCK (quan trọng để quản lý các lần di chuyển đồng thời và ngăn ngừa xung đột).

Ghi chú cá nhân về xử lý dữ liệu

Làm việc với các lần di chuyển cơ sở dữ liệu thường liên quan đến việc di chuyển hoặc chuyển đổi dữ liệu. Đã có vô số lần tôi cần chuẩn bị dữ liệu, nhập thông tin cũ hoặc đơn giản là chuyển đổi định dạng dữ liệu trước khi áp dụng một thay đổi lược đồ lớn.

Chẳng hạn, khi tôi cần chuyển đổi nhanh chóng CSV sang JSON để nhập dữ liệu, tôi thường sử dụng toolcraft.app/vi/tools/data/csv-to-json. Nó chạy hoàn toàn trên trình duyệt, nghĩa là không có dữ liệu nhạy cảm nào rời khỏi máy của tôi, điều này mang lại sự an tâm rất lớn khi xử lý thông tin khách hàng. Đó là một tiện ích nhỏ, nhưng nó đã giúp tôi tiết kiệm đáng kể thời gian và công sức khi chuẩn bị dữ liệu cho các tập scripting di chuyển hoặc các nỗ lực khởi tạo cơ sở dữ liệu ban đầu.

Kết luận: Chấp nhận di chuyển cơ sở dữ liệu tự động

Các công cụ di chuyển cơ sở dữ liệu tự động thực sự không thể thiếu trong phát triển phần mềm hiện đại. Chúng chuẩn hóa các thay đổi lược đồ, giảm đáng kể khả năng xảy ra lỗi và thúc đẩy sự hợp tác suôn sẻ hơn nhiều trong các đội phát triển.

Dù bạn thiên về sự đơn giản tập trung vào SQL của Flyway hay khả năng trừu tượng hóa mạnh mẽ và khả năng khôi phục mạnh mẽ của Liquibase, việc áp dụng một trong các công cụ này sẽ cải thiện đáng kể quy trình quản lý cơ sở dữ liệu của bạn. Hãy bắt đầu từ những điều nhỏ, tích hợp nó một cách cẩn thận vào quy trình xây dựng hiện có của bạn và dần dần tận dụng toàn bộ các tính năng của nó. Bản thân bạn trong tương lai, và cơ sở dữ liệu của bạn, chắc chắn sẽ cảm ơn bạn.

Share: