Skip to content

第二十三章:升級策略與遷移指引

學習目標

  • 掌握 ABP 版本升級的安全流程
  • 實作資料庫遷移與回滾
  • 理解從 ASP.NET Boilerplate 遷移的要點
  • 設計向後相容性與 Breaking Changes 管理

先備知識

  • 已完成第一至六章(基礎與基礎設施)
  • 熟悉 Entity Framework Core migrations
  • 了解 CI/CD 與版本控制

正文

一、ABP 版本升級流程

升級檢查清單

  1. 審視 Release Notes:識別 Breaking Changes 與新功能
  2. 備份資料庫:確保可恢復
  3. 在分支測試:不在主分支直接升級
  4. 測試關鍵功能:單元、整合測試應全數通過
  5. 演練在 Staging:與生產環境相同配置
  6. 逐步推出:先金絲雀、後全量

1.1 從 V10.0 升級至 V10.0 特別指南

ABP V10.0 是一個重大版本更新,包含以下關鍵變更:

  1. .NET 10 升級

    • 您的解決方案必須升級至 .NET 10。
    • 更新 global.json (若有) 與所有 .csproj 中的 <TargetFramework>net10.0
    • 更新 Dockerfile 中的 Base Image。
  2. Mapperly 取代 AutoMapper

    • V10 預設使用 Mapperly。雖然 AutoMapper 仍受支援,但建議逐步遷移以獲得效能優勢。
    • 若您沒有 AutoMapper 的商業授權,強烈建議遷移至 Mapperly。
  3. EF Core 遷移

    • 升級後,請務必執行 Add-Migration,因為 OpenIddict 模組 (v7.x) 可能有資料庫結構變更。

升級命令

bash()

bash
# bash
# 檢查當前版本
dotnet package search Volo.Abp --exact-match | head -5

# 更新 ABP 套件
dotnet package update --filter Volo.Abp* --version 10.0.0

# 或手動修改 .csproj
# <PackageReference Include="Volo.Abp" Version="10.0.0" />

升級後步驟

bash()

bash
# bash
# 1. 還原套件
dotnet restore

# 2. 編譯檢查相容性
dotnet build

# 3. 執行測試
dotnet test

# 4. 建立 migration
dotnet ef migrations add UpgradeToV10 -p src/MyApp.EntityFrameworkCore -s src/MyApp.DbMigrator

# 5. 在開發環境測試遷移
dotnet ef database update -p src/MyApp.EntityFrameworkCore -s src/MyApp.DbMigrator

# 6. 檢查遷移指令碼(務必審視!)

二、資料庫遷移策略

漸進式遷移(推薦)

bash()

bash
# bash
# 步驟 1:在測試環境完整測試新 migration
dotnet ef database update --connection "TestConnection"

# 步驟 2:驗證資料完整性
# 執行檢查查詢...
SELECT COUNT(*) FROM Books; -- 應與舊系統一致

# 步驟 3:在 Staging 演練完整升級 + 回滾
dotnet ef database update
# ... 驗證應用功能 ...
dotnet ef database update --target PreviousMigration  # 回滾

# 步驟 4:生產環境 - 先備份後升級
# mysqldump -u root -p bookstore > backup_$(date +%Y%m%d).sql
dotnet ef database update --connection "ProductionConnection"

自訂 Migration 處理複雜邏輯

csharp()

csharp
// csharp - Migrations/20251119_AddBookCategory.cs
public partial class AddBookCategory : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        // 新增欄位
        migrationBuilder.AddColumn<string>(
            name: "Category",
            table: "Books",
            type: "nvarchar(max)",
            nullable: true);

        // 資料遷移:根據現有欄位值設定新欄位
        migrationBuilder.Sql(@"
            UPDATE Books
            SET Category = CASE
                WHEN Price > 100 THEN 'Premium'
                WHEN Price > 50 THEN 'Standard'
                ELSE 'Budget'
            END
        ");

        // 設為非 NULL
        migrationBuilder.AlterColumn<string>(
            name: "Category",
            table: "Books",
            type: "nvarchar(max)",
            nullable: false);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(name: "Category", table: "Books");
    }
}

三、多租戶環境下的資料庫遷移

為每租戶執行遷移

bash()

bash
# bash - 指令碼:migrate-all-tenants.sh
#!/bin/bash

TENANTS=("tenant1" "tenant2" "tenant3")

for tenant in "${TENANTS[@]}"; do
    echo "Migrating $tenant..."

    # 設定租戶特定連線字串
    CONNECTION="Server=db-server;Database=bookstore_$tenant;User Id=sa;Password=Pass"

    dotnet ef database update \
        --connection "$CONNECTION" \
        -p src/MyApp.EntityFrameworkCore \
        -s src/MyApp.DbMigrator

    if [ $? -eq 0 ]; then
        echo "$tenant migrated successfully"
    else
        echo "ERROR: Failed to migrate $tenant"
        exit 1
    fi
done

echo "All tenants migrated!"

四、向後相容性與 Breaking Changes

版本號規則(SemVer)

  • Major(主版本):破壞性變更(如 API 移除、參數改變)
  • Minor(次版本):新功能,向下相容
  • Patch(修訂版本):修復,向下相容

處理 Breaking Changes

csharp()

csharp
// csharp - 升級前的做法(已過時)
public class BookAppService
{
    [Obsolete("使用 GetBookPagedAsync 代替", error: true)]
    public async Task<List<BookDto>> GetBooksAsync()
    {
        return await GetBookPagedAsync(0, 100);
    }

    // 新方法
    public async Task<PagedResultDto<BookDto>> GetBookPagedAsync(int skipCount, int maxResultCount)
    {
        // ...
    }
}

使用 Feature Toggle 漸進遷移

csharp()

csharp
// csharp
public class BookAppService
{
    private readonly IFeatureChecker _featureChecker;

    public BookAppService(IFeatureChecker featureChecker)
    {
        _featureChecker = featureChecker;
    }

    public async Task<IEnumerable<BookDto>> GetBooksAsync()
    {
        // 根據 Feature 使用新舊方法
        if (await _featureChecker.IsEnabledAsync("UseNewBookApi"))
        {
            return await GetBooksV2Async(); // 新邏輯
        }
        return await GetBooksV1Async(); // 舊邏輯
    }
}

五、從 ASP.NET Boilerplate 遷移

遷移評估

  • 識別使用的 ABP v2 或 Boilerplate 版本
  • 列舉自訂擴充與第三方模組
  • 評估功能差異(某些功能社群版可能無)

逐模組遷移策略

Step 1:建立新 ABP Framework 專案

bash()

bash
# bash
abp new MyApp -t app

Step 2:遷移 Domain 層

csharp()

csharp
// csharp - 舊 Boilerplate 實體
public class Book : Entity<Guid>
{
    public string Title { get; set; }
}

// csharp - 新 ABP 實體
public class Book : FullAuditedAggregateRoot<Guid>
{
    public string Title { get; set; }

    public Book() { }
    public Book(Guid id, string title) : base(id) => Title = title;
}

Step 3:遷移 Application 層

csharp()

csharp
// csharp - 舊 Boilerplate AppService
public class BookAppService : ApplicationService, IBookAppService
{
    private readonly IBookRepository _bookRepository;

    public BookAppService(IBookRepository bookRepository)
    {
        _bookRepository = bookRepository;
    }
}

// csharp - 新 ABP AppService(基本相同)
public class BookAppService : ApplicationService, IBookAppService
{
    private readonly IRepository<Book, Guid> _bookRepository;

    public BookAppService(IRepository<Book, Guid> bookRepository)
    {
        _bookRepository = bookRepository;
    }
}

Step 4:資料庫遷移

使用 ETL 工具或自訂指令碼逐表遷移

sql()

sql
-- sql - 遷移 Books 表
INSERT INTO NewSystem.Books (Id, Title, Author, CreationTime, CreatorId)
SELECT Id, Title, Author, CreationTime, CreatorId
FROM OldSystem.dbo.Books
WHERE Id NOT IN (SELECT Id FROM NewSystem.Books);

-- 驗證計數
SELECT COUNT(*) FROM OldSystem.dbo.Books;
SELECT COUNT(*) FROM NewSystem.Books;

Step 5:雙寫驗證(過渡期)

csharp()

csharp
// csharp - 在遷移期間同時寫入新舊系統
public class DualWriteBookService
{
    private readonly OldBookService _oldService;
    private readonly NewBookService _newService;

    public async Task CreateBookAsync(CreateBookDto input)
    {
        var oldResult = await _oldService.CreateAsync(input);
        var newResult = await _newService.CreateAsync(input);

        if (oldResult.Id != newResult.Id)
            throw new InvalidOperationException("Dual-write mismatch");
    }
}

六、回滾方案

快速回滾檢查清單

  • [ ] 備份資料庫已驗證可還原
  • [ ] 舊版本 Docker 映像仍可用
  • [ ] 資料庫回滾指令已測試
  • [ ] 通訊錄與回滾負責人已確認

Kubernetes 藍綠部署回滾

bash()

bash
# bash
# 部署失敗時立即回滾
kubectl rollout undo deployment/myapp

# 查看回滾歷史
kubectl rollout history deployment/myapp

# 回滾至特定版本
kubectl rollout undo deployment/myapp --to-revision=5

資料庫回滾

bash()

bash
# bash
# 1. 停止應用
kubectl scale deployment myapp --replicas=0

# 2. 還原資料庫備份
mysql -u root -p bookstore < backup_20251119.sql

# 3. 恢復舊版本應用
kubectl set image deployment/myapp myapp=myregistry/myapp:9.3.0
kubectl scale deployment myapp --replicas=3

# 4. 驗證應用正常
curl http://myapp-svc/health

七、升級檢查清單

text()

升級前準備
- [ ] 備份資料庫與完整系統
- [ ] 審視升級版本的 Release Notes
- [ ] 檢查依賴套件相容性(Volo.Abp、EF Core 等)
- [ ] 在分支建立升級分支

測試階段
- [ ] 本地完整編譯與測試
- [ ] 執行所有自動化測試(單元 + 整合 + E2E)
- [ ] 手動測試關鍵業務流程
- [ ] 檢查資料庫遷移指令碼邏輯

Staging 驗證
- [ ] 部署至 Staging 環境
- [ ] 執行 smoke tests 與回歸測試
- [ ] 驗證資料遷移完整性
- [ ] 測試回滾流程

生產部署
- [ ] 建立 incident 標籤與溝通管道
- [ ] 先金絲雀:部署至 1-2 伺服器,監控 24 小時
- [ ] 全量部署:逐批升級剩餘伺服器
- [ ] 監控應用指標、日誌、錯誤率
- [ ] 準備回滾方案隨時執行

八、實務最佳實務

  • 升級前充分測試,不要過於激進
  • 使用 Feature Toggles 管理新舊功能切換
  • 保持舊版本可用以便快速回滾
  • 文件化所有升級步驟與破壞性變更
  • 社群活躍項目通常升級風險更低
  • 定期升級,避免跨度太大的版本跳躍

實作練習

  1. 執行一次完整升級流程(本地 → Staging → 回滾)
  2. 建立複雜資料庫遷移,包含資料轉換邏輯
  3. 設計多租戶環境下的升級策略
  4. 評估與規劃一次遷移(如 Boilerplate → ABP Framework)

習題(至少 6 題)

概念題(易)

  1. SemVer 中的主、次、修訂版本各代表什麼?(難度:易)
  2. 為何升級前應備份資料庫?(難度:中)

計算 / 練習題(中) 3. 設計 50 個租戶的升級策略,評估時間與風險。(難度:中) 4. 比較三種遷移方式(停機升級、漸進升級、藍綠部署),各自的優缺點。(難度:中)

實作 / 編碼題(較難) 5. 實作完整的資料庫遷移(含複雜資料轉換、驗證、回滾)。(難度:較難) 6. 設計與實作多租戶系統的升級流程(含 Feature Toggle、回滾、監控)。(難度:較難)

術語表

  • Breaking Change(破壞性變更):修改會導致既有代碼無法運行
  • Migration(遷移):資料庫結構或應用版本的轉換
  • Rollback(回滾):恢復至先前版本或資料狀態
  • Canary Deployment(金絲雀部署):先部署新版本至小部分用戶進行驗證

參考資料

習題解答:content/solutions/ch23-solutions.md

Released under the MIT License.