1. Tổng quan về Cơ chế Di cư

1.1 Khái niệm và Vai trò của Di cư

Di cư cơ sở dữ liệu là quá trình đồng bộ hóa các thay đổi trong mô hình dữ liệu với cấu trúc cơ sở dữ liệu, đó là điều quan trọng cho tính liên tục của dữ liệu. Khi phiên bản ứng dụng tiến hành lặp lại, mô hình dữ liệu thường trải qua các thay đổi, như thêm hoặc xóa trường, hoặc sửa đổi các chỉ mục. Di cư cho phép các nhà phát triển quản lý những thay đổi này theo cách có phiên bản và có hệ thống, đảm bảo sự nhất quán giữa cấu trúc cơ sở dữ liệu và mô hình dữ liệu.

Trong phát triển web hiện đại, cơ chế di cư cung cấp những lợi ích sau:

  1. Kiểm soát Phiên bản: Các tệp di cư có thể theo dõi lịch sử thay đổi của cấu trúc cơ sở dữ liệu, làm cho việc quay lại và hiểu rõ những thay đổi trong mỗi phiên bản trở nên thuận tiện.
  2. Triển khai Tự động: Thông qua cơ chế di cư, triển khai và cập nhật cơ sở dữ liệu có thể được tự động hóa, giảm thiểu khả năng can thiệp thủ công và rủi ro lỗi.
  3. Hợp tác Nhóm: Các tệp di cư đảm bảo rằng các thành viên nhóm sử dụng cấu trúc cơ sở dữ liệu đồng bộ trong các môi trường phát triển khác nhau, tạo điều kiện cho việc phát triển cộng tác.

1.2 Đặc điểm Di cư của Khung ent

Việc tích hợp khung ent với cơ chế di cư cung cấp những đặc điểm sau:

  1. Lập trình Mô tả: Các nhà phát triển chỉ cần tập trung vào biểu diễn Go của các thực thể, và khung ent sẽ xử lý việc chuyển đổi các thực thể sang các bảng cơ sở dữ liệu.
  2. Di cư Tự động: ent có thể tự động tạo và cập nhật cấu trúc bảng cơ sở dữ liệu mà không cần viết các câu lệnh DDL thủ công.
  3. Kiểm soát Linh hoạt: ent cung cấp các tùy chọn cấu hình khác nhau để hỗ trợ các yêu cầu di cư khác nhau, như có hoặc không có ràng buộc khóa ngoại, và tạo ID toàn cầu duy nhất.

2. Giới thiệu về Di cư Tự động

2.1 Nguyên lý cơ bản của Di cư Tự động

Tính năng di cư tự động của khung ent dựa trên các tệp định nghĩa schema (thường được tìm thấy trong thư mục ent/schema) để tạo cấu trúc cơ sở dữ liệu. Sau khi các nhà phát triển xác định thực thể và mối quan hệ, ent sẽ kiểm tra cấu trúc hiện có trong cơ sở dữ liệu và tạo ra các hoạt động tương ứng để tạo bảng, thêm hoặc sửa đổi cột, tạo chỉ mục, v.v.

Hơn nữa, nguyên lý di cư tự động của ent hoạt động theo "chế độ nối thêm": mặc định chỉ thêm bảng mới, chỉ thêm chỉ mục mới, hoặc thêm cột vào bảng, và không xóa bảng hoặc cột hiện có. Thiết kế này có lợi ích trong việc ngăn chặn mất dữ liệu tình cờ và làm cho việc mở rộng cấu trúc cơ sở dữ liệu trở nên dễ dàng theo hướng tiến về phía trước.

2.2 Sử dụng Di cư Tự động

Các bước cơ bản để sử dụng di cư tự động của ent như sau:

package main

import (
    "context"
    "log"
    "ent"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("Không thể kết nối với MySQL: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Thực hiện di cư tự động để tạo hoặc cập nhật cấu trúc cơ sở dữ liệu
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("Không thể tạo cấu trúc cơ sở dữ liệu: %v", err)
    }
}

Trong đoạn mã trên, ent.Open chịu trách nhiệm thiết lập kết nối với cơ sở dữ liệu và trả về một phiên bản khách hàng, trong khi client.Schema.Create thực hiện thực tế hoạt động di cư tự động.

3. Ứng dụng Nâng cao của Di cư Tự động

3.1 Xóa Cột và Chỉ mục

Trong một số trường hợp, chúng ta có thể cần loại bỏ các cột hoặc chỉ mục không còn cần thiết từ cấu trúc cơ sở dữ liệu. Lúc này, chúng ta có thể sử dụng các tùy chọn WithDropColumnWithDropIndex. Ví dụ:

// Chạy di cư với các tùy chọn để xóa cột và chỉ mục.
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

Đoạn mã này cho phép cấu hình xóa cột và chỉ mục trong quá trình di cư tự động. ent sẽ xóa bất kỳ cột và chỉ mục nào không tồn tại trong định nghĩa schema khi thực hiện di cư.

3.2 ID Toàn cầu Duy nhất

Mặc định, các khóa chính trong cơ sở dữ liệu SQL bắt đầu từ 1 cho mỗi bảng, và các loại thực thể khác nhau có thể chia sẻ cùng một ID. Trong một số kịch bản ứng dụng, như khi sử dụng GraphQL, có thể cần cung cấp tính duy nhất toàn cầu cho các ID của đối tượng thuộc các loại thực thể khác nhau. Trong ent, điều này có thể được cấu hình bằng cách sử dụng tùy chọn WithGlobalUniqueID:

// Chạy di cư với ID duy nhất toàn cầu cho mỗi thực thể.
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("Không thể tạo cấu trúc cơ sở dữ liệu: %v", err)
}

Sau khi bật tùy chọn WithGlobalUniqueID, ent sẽ gán một ID phạm vi 2^32 cho mỗi thực thể trong một bảng có tên ent_types để đạt được tính duy nhất toàn cầu.

3.3 Chế Độ Ngoại Tuyến

Chế độ ngoại tuyến cho phép viết các thay đổi schema vào một io.Writer thay vì thực thi chúng trực tiếp trên cơ sở dữ liệu. Nó hữu ích để xác minh các lệnh SQL trước khi thay đổi có hiệu lực, hoặc để tạo script SQL để thực thi thủ công. Ví dụ:

// Ghi các thay đổi migration vào một file
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("Không thể tạo file migration: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("Không thể in các thay đổi schema của cơ sở dữ liệu: %v", err)
}

Mã này sẽ ghi các thay đổi migration vào một file có tên là migrate.sql. Trong thực tế, các nhà phát triển có thể chọn in trực tiếp ra đầu ra chuẩn hoặc ghi vào một file để xem xét hoặc lưu giữ.

4. Hỗ Trợ Khóa Ngoại và Hook Tùy Chỉnh

4.1 Bật hoặc Tắt Khóa Ngoại

Trong Ent, khóa ngoại được thực hiện bằng cách xác định mối quan hệ (cạnh) giữa các thực thể, và các mối quan hệ khóa ngoại này được tạo tự động ở cấp độ cơ sở dữ liệu để áp dụng tính toàn vẹn và nhất quán dữ liệu. Tuy nhiên, trong một số tình huống, như tối ưu hiệu suất hoặc khi cơ sở dữ liệu không hỗ trợ khóa ngoại, bạn có thể chọn tắt chúng.

Để bật hoặc tắt ràng buộc khóa ngoại trong các migration, bạn có thể kiểm soát điều này thông qua tùy chọn cấu hình WithForeignKeys:

// Bật khóa ngoại
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("Lỗi khi tạo nguồn lực schema với khóa ngoại: %v", err)
}

// Tắt khóa ngoại
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("Lỗi khi tạo nguồn lực schema không có khóa ngoại: %v", err)
}

Tùy chọn cấu hình này cần được truyền khi gọi Schema.Create, và nó xác định liệu có bao gồm ràng buộc khóa ngoại trong DDL được tạo ra dựa trên giá trị cụ thể hay không.

4.2 Ứng Dụng Các Hook Trong Migration

Hook migration là logic tùy chỉnh có thể được chèn và thực thi tại các giai đoạn khác nhau của quá trình thực thi migration. Chúng rất hữu ích để thực hiện logic cụ thể trên cơ sở dữ liệu trước/sau khi di chuyển, như xác minh kết quả di chuyển và điền trước dữ liệu.

Dưới đây là một ví dụ về cách thực hiện các hook migration tùy chỉnh:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // Code tùy chỉnh để thực thi trước khi migration
        // Ví dụ: logging, kiểm tra điều kiện cụ thể, v.v.
        log.Println("Logic tùy chỉnh trước khi migration")
        
        // Gọi hook tiếp theo hoặc logic migration mặc định
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // Code tùy chỉnh để thực thi sau khi migration
        // Ví dụ: dọn dẹp, di chuyển dữ liệu, kiểm tra bảo mật, v.v.
        log.Println("Logic tùy chỉnh sau khi migration")
        return nil
    })
}

// Sử dụng hook tùy chỉnh trong migration
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("Lỗi khi áp dụng hook migration tùy chỉnh: %v", err)
}

Hook là công cụ mạnh mẽ và không thể thiếu cho các migration phức tạp, cho phép bạn kiểm soát trực tiếp hành vi di chuyển cơ sở dữ liệu khi cần thiết.

5. Migration Theo Phiên Bản

5.1 Giới Thiệu về Migration Theo Phiên Bản

Migration theo phiên bản là một mô hình quản lý di chuyển cơ sở dữ liệu, cho phép nhà phát triển chia thay đổi cấu trúc cơ sở dữ liệu thành nhiều phiên bản, mỗi phiên bản chứa một bộ lệnh sửa đổi cơ sở dữ liệu cụ thể. So với Auto Migration, migration theo phiên bản cung cấp kiểm soát tốt hơn, đảm bảo tính theo dõi và khả nghịch của các thay đổi cấu trúc cơ sở dữ liệu.

Ưu điểm chính của migration theo phiên bản là hỗ trợ di chuyển về phía trước và phía sau (tức là nâng cấp hoặc hạ cấp), cho phép nhà phát triển áp dụng, quay trở lại hoặc bỏ qua các thay đổi cụ thể khi cần thiết. Khi hợp tác trong một nhóm, migration theo phiên bản đảm bảo rằng mỗi thành viên làm việc trên cùng cấu trúc cơ sở dữ liệu, giảm thiểu vấn đề gây ra bởi sự không nhất quán.

Auto migration thường không thể hồi phục, tạo và thực thi các câu lệnh SQL để phù hợp với trạng thái mới nhất của mô hình thực thể, chủ yếu được sử dụng trong các giai đoạn phát triển hoặc dự án nhỏ.

5.2 Sử Dụng Migration Theo Phiên Bản

1. Cài đặt công cụ Atlas

Trước khi sử dụng phiên bản di dời, bạn cần cài đặt công cụ Atlas trên hệ thống của bạn. Atlas là một công cụ di dời cơ sở dữ liệu hỗ trợ nhiều hệ thống cơ sở dữ liệu, cung cấp các tính năng mạnh mẽ để quản lý thay đổi cấu trúc cơ sở dữ liệu.

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. Tạo các Tập Tin Di Dời Dựa Trên Định Nghĩa Thực Thể Hiện Tại

atlas migrate diff migration_name \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. Tập Tin Di Dời của Ứng Dụng

Sau khi tạo các tập tin di dời, chúng có thể được áp dụng vào môi trường phát triển, kiểm thử hoặc sản xuất. Thông thường, bạn sẽ áp dụng các tập tin di dời này vào cơ sở dữ liệu phát triển hoặc kiểm thử trước để đảm bảo rằng chúng thực thi như mong đợi. Sau đó, các bước di dời tương tự sẽ được thực thi trong môi trường sản xuất.

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

Sử dụng lệnh atlas migrate apply, chỉ định thư mục tập tin di dời (--dir) và URL của cơ sở dữ liệu đích (--url) để áp dụng các tập tin di dời.