dramaling-vocab-learning/backend/DramaLing.Api/DEVELOPMENT_GUIDE.md

14 KiB

DramaLing API 開發指南

版本: 1.0 最後更新: 2025-09-30 適用對象: 後端開發者、新團隊成員

🚀 快速開始

開發環境要求

  • .NET 8 SDK (最新 LTS 版本)
  • Visual Studio CodeVisual Studio 2022
  • Git 版本控制
  • SQLite (開發環境) / SQL Server (生產環境)

環境變數配置

建立 .env 檔案或設定系統環境變數:

# Gemini AI 配置
DRAMALING_GEMINI_API_KEY=your-gemini-api-key

# Supabase 認證配置
DRAMALING_SUPABASE_URL=your-supabase-url
DRAMALING_SUPABASE_JWT_SECRET=your-jwt-secret

# Replicate AI 配置
DRAMALING_REPLICATE_API_TOKEN=your-replicate-token

# Azure Speech 配置
DRAMALING_AZURE_SPEECH_KEY=your-azure-speech-key
DRAMALING_AZURE_SPEECH_REGION=your-region

# 資料庫連接 (可選,預設使用 SQLite)
DRAMALING_DB_CONNECTION=your-connection-string

# 測試環境
USE_INMEMORY_DB=true  # 測試時使用記憶體資料庫

🏗️ 開發工作流程

1. 專案設定

# Clone 專案
git clone <repository-url>
cd dramaling-vocab-learning/backend/DramaLing.Api

# 安裝相依套件
dotnet restore

# 執行資料庫遷移
dotnet ef database update

# 啟動開發伺服器
dotnet run

# 訪問 Swagger UI
open https://localhost:7001/swagger

2. 開發分支策略

# 主要分支
main                    # 生產環境代碼
develop                 # 開發整合分支

# 功能分支命名規則
feature/user-auth      # 新功能開發
bugfix/cache-issue     # Bug 修復
hotfix/security-patch  # 緊急修復
refactor/clean-arch    # 重構改善

📝 編碼規範

1. C# 編碼標準

命名規則:

// 類別和介面 - PascalCase
public class FlashcardService { }
public interface IFlashcardRepository { }

// 方法和屬性 - PascalCase
public async Task<Flashcard> GetByIdAsync(Guid id) { }
public string UserName { get; set; }

// 私有欄位 - camelCase with underscore
private readonly ILogger<FlashcardService> _logger;

// 參數和區域變數 - camelCase
public void ProcessData(string inputData)
{
    var processedResult = Transform(inputData);
}

// 常數 - PascalCase
public const int MaxRetryCount = 3;

異步方法規範:

// ✅ 正確:異步方法使用 Async 後綴
public async Task<User> GetUserAsync(Guid userId) { }

// ✅ 正確:使用 ConfigureAwait(false) 在類別庫中
var result = await httpClient.GetAsync(url).ConfigureAwait(false);

// ❌ 錯誤:同步調用異步方法
var user = GetUserAsync(id).Result; // 可能導致死鎖

2. 資料夾和檔案組織

Services/Domain/Feature/
├── IFeatureService.cs      # 介面定義
├── FeatureService.cs       # 實現
├── FeatureModels.cs        # 相關模型 (如果簡單)
└── README.md              # 服務說明 (複雜功能)

Tests/Unit/Services/Domain/
└── FeatureServiceTests.cs  # 對應測試

3. 文檔註解標準

/// <summary>
/// 根據使用者 ID 獲取單字卡列表
/// </summary>
/// <param name="userId">使用者唯一識別碼</param>
/// <param name="search">搜尋關鍵字,可為空</param>
/// <param name="favoritesOnly">是否只顯示收藏的單字卡</param>
/// <returns>符合條件的單字卡列表</returns>
/// <exception cref="ArgumentNullException">當 userId 為空時拋出</exception>
public async Task<IEnumerable<Flashcard>> GetByUserIdAsync(
    Guid userId,
    string? search = null,
    bool favoritesOnly = false)
{
    // 實作邏輯...
}

🧩 功能開發指南

1. 新增 API 端點

步驟 1: 定義 DTO

// Models/DTOs/Feature/
public class CreateFeatureRequest
{
    [Required]
    public string Name { get; set; } = string.Empty;

    [StringLength(500)]
    public string? Description { get; set; }
}

public class FeatureResponse
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; }
}

步驟 2: 建立 Repository (如需要)

// Repositories/IFeatureRepository.cs
public interface IFeatureRepository : IRepository<Feature>
{
    Task<IEnumerable<Feature>> GetByUserIdAsync(Guid userId);
    Task<Feature?> GetByNameAsync(string name);
}

// Repositories/FeatureRepository.cs
public class FeatureRepository : BaseRepository<Feature>, IFeatureRepository
{
    public FeatureRepository(DramaLingDbContext context, ILogger<BaseRepository<Feature>> logger)
        : base(context, logger) { }

    public async Task<IEnumerable<Feature>> GetByUserIdAsync(Guid userId)
    {
        return await DbSet.Where(f => f.UserId == userId).ToListAsync();
    }
}

步驟 3: 實作 Service

// Services/Domain/IFeatureService.cs
public interface IFeatureService
{
    Task<FeatureResponse> CreateAsync(CreateFeatureRequest request);
    Task<IEnumerable<FeatureResponse>> GetByUserIdAsync(Guid userId);
}

// Services/Domain/FeatureService.cs
public class FeatureService : IFeatureService
{
    private readonly IFeatureRepository _repository;
    private readonly ILogger<FeatureService> _logger;

    public FeatureService(IFeatureRepository repository, ILogger<FeatureService> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task<FeatureResponse> CreateAsync(CreateFeatureRequest request)
    {
        var feature = new Feature
        {
            Name = request.Name,
            Description = request.Description,
            CreatedAt = DateTime.UtcNow
        };

        await _repository.AddAsync(feature);

        return new FeatureResponse
        {
            Id = feature.Id,
            Name = feature.Name,
            CreatedAt = feature.CreatedAt
        };
    }
}

步驟 4: 建立 Controller

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class FeatureController : ControllerBase
{
    private readonly IFeatureService _featureService;

    public FeatureController(IFeatureService featureService)
    {
        _featureService = featureService;
    }

    /// <summary>
    /// 建立新功能
    /// </summary>
    [HttpPost]
    public async Task<ActionResult<FeatureResponse>> CreateFeature(CreateFeatureRequest request)
    {
        var result = await _featureService.CreateAsync(request);
        return CreatedAtAction(nameof(GetFeature), new { id = result.Id }, result);
    }
}

步驟 5: 註冊服務

// Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddBusinessServices(this IServiceCollection services)
{
    // ... 其他服務
    services.AddScoped<IFeatureRepository, FeatureRepository>();
    services.AddScoped<IFeatureService, FeatureService>();

    return services;
}

2. 撰寫單元測試

// Tests/Unit/Services/FeatureServiceTests.cs
public class FeatureServiceTests : TestBase
{
    private readonly IFeatureService _service;
    private readonly Mock<IFeatureRepository> _mockRepository;

    public FeatureServiceTests()
    {
        _mockRepository = new Mock<IFeatureRepository>();
        _service = new FeatureService(_mockRepository.Object, Mock.Of<ILogger<FeatureService>>());
    }

    [Fact]
    public async Task CreateAsync_ValidRequest_ShouldReturnFeatureResponse()
    {
        // Arrange
        var request = new CreateFeatureRequest { Name = "Test Feature" };
        var expectedFeature = new Feature { Id = Guid.NewGuid(), Name = "Test Feature" };

        _mockRepository.Setup(r => r.AddAsync(It.IsAny<Feature>()))
                      .Returns(Task.CompletedTask)
                      .Callback<Feature>(f => f.Id = expectedFeature.Id);

        // Act
        var result = await _service.CreateAsync(request);

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be("Test Feature");
        _mockRepository.Verify(r => r.AddAsync(It.IsAny<Feature>()), Times.Once);
    }
}

🧪 測試策略

1. 測試分類

單元測試 - 隔離測試個別類別

[Fact]
[Trait("Category", "Unit")]
public async Task ServiceMethod_ValidInput_ReturnsExpectedResult()
{
    // AAA 模式測試
}

整合測試 - 測試多個組件協作

[Fact]
[Trait("Category", "Integration")]
public async Task ApiEndpoint_ValidRequest_ReturnsCorrectResponse()
{
    // 使用 TestServer 測試整個請求流程
}

端到端測試 - 完整使用者場景

[Fact]
[Trait("Category", "E2E")]
public async Task UserWorkflow_CompleteScenario_WorksCorrectly()
{
    // 模擬真實使用者操作流程
}

2. 測試執行

# 執行所有測試
dotnet test

# 執行特定分類測試
dotnet test --filter "Category=Unit"
dotnet test --filter "Category=Integration"

# 產生覆蓋率報告
dotnet test --collect:"XPlat Code Coverage"

# 執行特定測試類別
dotnet test --filter "ClassName=FeatureServiceTests"

🐛 除錯與診斷

1. 日誌記錄最佳實務

public class FeatureService : IFeatureService
{
    private readonly ILogger<FeatureService> _logger;

    public async Task<Feature> ProcessFeatureAsync(Guid featureId)
    {
        _logger.LogInformation("開始處理功能 {FeatureId}", featureId);

        try
        {
            var feature = await _repository.GetByIdAsync(featureId);
            if (feature == null)
            {
                _logger.LogWarning("功能不存在 {FeatureId}", featureId);
                throw new NotFoundException($"Feature {featureId} not found");
            }

            // 處理邏輯...

            _logger.LogInformation("功能處理完成 {FeatureId}", featureId);
            return feature;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "處理功能時發生錯誤 {FeatureId}", featureId);
            throw;
        }
    }
}

2. 效能監控

// 使用 Stopwatch 監控關鍵操作
using var activity = Activity.StartActivity("ProcessFeature");
var stopwatch = Stopwatch.StartNew();

try
{
    // 業務邏輯執行
    var result = await ProcessComplexOperation();

    stopwatch.Stop();
    _logger.LogInformation("操作完成,耗時 {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);

    return result;
}
catch (Exception ex)
{
    stopwatch.Stop();
    _logger.LogError(ex, "操作失敗,耗時 {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
    throw;
}

3. 常見問題診斷

問題: 資料庫連接失敗

# 檢查連接字串
dotnet user-secrets list

# 測試資料庫連接
dotnet ef database update --dry-run

問題: JWT 驗證失敗

// 在 Startup/Program.cs 中啟用詳細日誌
builder.Logging.AddFilter("Microsoft.AspNetCore.Authentication", LogLevel.Debug);

問題: 快取不工作

// 檢查快取配置和依賴注入
services.AddMemoryCache();
services.AddScoped<ICacheService, HybridCacheService>();

🔧 工具和擴展

1. 推薦 VS Code 擴展

// .vscode/extensions.json
{
    "recommendations": [
        "ms-dotnettools.csharp",
        "ms-dotnettools.vscode-dotnet-runtime",
        "bradlc.vscode-tailwindcss",
        "esbenp.prettier-vscode",
        "ms-vscode.vscode-json",
        "humao.rest-client"
    ]
}

2. EditorConfig 設定

# .editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.cs]
indent_style = space
indent_size = 4

[*.{json,yml,yaml}]
indent_style = space
indent_size = 2

3. Git 鉤子設定

# .githooks/pre-commit
#!/bin/sh
# 執行程式碼格式化
dotnet format --verify-no-changes

# 執行測試
dotnet test --no-build --verbosity quiet

# 執行靜態分析
dotnet build --verbosity quiet

📚 學習資源

1. 核心概念學習

  • Clean Architecture: Robert C. Martin 的 Clean Architecture 書籍
  • Domain-Driven Design: Eric Evans 的 DDD 經典著作
  • ASP.NET Core: Microsoft 官方文檔
  • Entity Framework Core: EF Core 官方指南

2. 最佳實務參考

  • Microsoft .NET Application Architecture Guides
  • Clean Code: Robert C. Martin
  • Refactoring: Martin Fowler
  • Design Patterns: Gang of Four

3. 社群資源

  • Stack Overflow: 問題解決
  • GitHub: 開源專案參考
  • Medium/Dev.to: 技術部落格
  • YouTube: 技術教學影片

常見問題 FAQ

Q: 如何新增一個新的 AI 服務?

A:

  1. Services/AI/ 下建立新的服務目錄
  2. 實作服務介面和具體類別
  3. ServiceCollectionExtensions.cs 註冊服務
  4. 撰寫單元測試
  5. 更新 Services/README.md 文檔

Q: 資料庫遷移失敗怎麼辦?

A:

# 檢查遷移狀態
dotnet ef migrations list

# 回滾到特定遷移
dotnet ef database update PreviousMigrationName

# 重新產生遷移
dotnet ef migrations add NewMigrationName

Q: 如何優化 API 效能?

A:

  1. 使用異步方法 (async/await)
  2. 實作適當的快取策略
  3. 最佳化資料庫查詢 (避免 N+1)
  4. 使用分頁載入大數據集
  5. 啟用 HTTP 壓縮和快取標頭

Q: 如何處理敏感資訊?

A:

# 使用 User Secrets (開發環境)
dotnet user-secrets set "ApiKey" "your-secret-key"

# 使用環境變數 (生產環境)
export DRAMALING_API_KEY="your-secret-key"

# 絕不在程式碼中硬編碼敏感資訊

🤝 貢獻指南

提交 Pull Request 前檢查清單

  • 程式碼遵循編碼規範
  • 所有測試通過
  • 新功能有對應的測試
  • 文檔已更新
  • Commit 訊息清楚描述變更
  • 沒有合併衝突

Commit 訊息格式

類型(範圍): 簡短描述

詳細描述(如果需要)

- 變更項目 1
- 變更項目 2

Closes #123

類型標籤:

  • feat: 新功能
  • fix: Bug 修復
  • docs: 文檔更新
  • style: 程式碼格式化
  • refactor: 重構
  • test: 測試相關
  • chore: 構建工具或輔助工具

文檔版本: 1.0 維護者: DramaLing 開發團隊 最後更新: 2025-09-30