# DramaLing API 開發指南

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

## 🚀 快速開始

### 開發環境要求

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

### 環境變數配置

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

```bash
# 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. 專案設定

```bash
# 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. 開發分支策略

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

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

---

## 📝 編碼規範

### 1. C# 編碼標準

**命名規則**:
```csharp
// 類別和介面 - 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;
```

**異步方法規範**:
```csharp
// ✅ 正確：異步方法使用 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. 文檔註解標準

```csharp
/// <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**
```csharp
// 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 (如需要)**
```csharp
// 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**
```csharp
// 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**
```csharp
[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: 註冊服務**
```csharp
// Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddBusinessServices(this IServiceCollection services)
{
    // ... 其他服務
    services.AddScoped<IFeatureRepository, FeatureRepository>();
    services.AddScoped<IFeatureService, FeatureService>();

    return services;
}
```

### 2. 撰寫單元測試

```csharp
// 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. 測試分類

**單元測試** - 隔離測試個別類別
```csharp
[Fact]
[Trait("Category", "Unit")]
public async Task ServiceMethod_ValidInput_ReturnsExpectedResult()
{
    // AAA 模式測試
}
```

**整合測試** - 測試多個組件協作
```csharp
[Fact]
[Trait("Category", "Integration")]
public async Task ApiEndpoint_ValidRequest_ReturnsCorrectResponse()
{
    // 使用 TestServer 測試整個請求流程
}
```

**端到端測試** - 完整使用者場景
```csharp
[Fact]
[Trait("Category", "E2E")]
public async Task UserWorkflow_CompleteScenario_WorksCorrectly()
{
    // 模擬真實使用者操作流程
}
```

### 2. 測試執行

```bash
# 執行所有測試
dotnet test

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

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

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

---

## 🐛 除錯與診斷

### 1. 日誌記錄最佳實務

```csharp
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. 效能監控

```csharp
// 使用 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. 常見問題診斷

**問題**: 資料庫連接失敗
```bash
# 檢查連接字串
dotnet user-secrets list

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

**問題**: JWT 驗證失敗
```csharp
// 在 Startup/Program.cs 中啟用詳細日誌
builder.Logging.AddFilter("Microsoft.AspNetCore.Authentication", LogLevel.Debug);
```

**問題**: 快取不工作
```csharp
// 檢查快取配置和依賴注入
services.AddMemoryCache();
services.AddScoped<ICacheService, HybridCacheService>();
```

---

## 🔧 工具和擴展

### 1. 推薦 VS Code 擴展

```json
// .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 設定

```ini
# .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 鉤子設定

```bash
# .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:
```bash
# 檢查遷移狀態
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:
```bash
# 使用 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