跳至主要内容

GST 切面使用規範

適用範圍:所有使用 GST 底層框架的應用層專案

最後更新:2026-02-03


概述

GST 底層框架提供完整的 Metalama 切面設計(GST.Core.Aspects),應用層專案必須正確使用這些切面,以確保:

  • 統一的 Logging 格式與行為
  • FDA Part 11 合規性(稽核追蹤)
  • 一致的例外處理與重試機制
  • 可維護的橫切關注點管理

切面所在位置

專案命名空間NuGet
GST.Core.AspectsGST.Core.Aspects.*內部套件

命名空間對照

命名空間切面類別
GST.Core.Aspects.Logging[Log], [LogPerformance], [LogException]
GST.Core.Aspects.Audit[Audit], [Part11Audit], [AuditDataChange]
GST.Core.Aspects.Caching[Cache], [CacheInvalidate]
GST.Core.Aspects.Exception[HandleException], [CircuitBreaker], [Retry]
GST.Core.Aspects.Validation[NotNull], [NotEmpty], [Range]
GST.Core.Aspects.Authorization[Authorize], [RequirePermission]
GST.Core.Aspects.Performance[Throttle], [Debounce]
GST.Core.Aspects.Threading[RunOnUiThread], [Synchronized]
GST.Core.Aspects.Localization[Localized]
GST.Core.Aspects.Observability[NotifyPropertyChanged]

可用切面清單

1. Logging 切面

切面用途參數
[Log]記錄方法進入/退出LogParameters, LogReturnValue, LogExceptions
[LogPerformance]記錄執行時間ThresholdMs(超過才記錄)
[LogException]記錄例外資訊-
[Log(LogParameters = true, LogReturnValue = true)]
public async Task<Recipe> GetRecipeAsync(int id)
{
// 自動記錄:方法進入、參數、返回值、例外
}

[LogPerformance(ThresholdMs = 100)]
public void ExpensiveOperation()
{
// 僅當執行超過 100ms 才記錄
}

2. Audit 切面(FDA Part 11)

切面用途參數
[Audit]基本稽核OperationName, IncludeParameters
[Part11Audit]FDA 合規稽核EntityType, RequireChangeReason
[AuditDataChange]資料變更追蹤EntityType, OldValueParameter
[RequireChangeReason]強制變更理由-
[Part11Audit(EntityType = "Recipe", RequireChangeReason = true)]
public async Task UpdateRecipeAsync(int id, Recipe recipe, string changeReason)
{
// 自動記錄:User、timestamp、Hash Chain、完整性驗證
}

[AuditDataChange(EntityType = "Recipe", OldValueParameter = "existing")]
public void Update(Recipe existing, Recipe newValues)
{
// 自動記錄修改前後的值
}

3. Exception 切面

切面用途參數
[HandleException]捕捉並處理例外Suppress
[CircuitBreaker]熔斷器模式FailureThreshold, BreakDurationMs
[Retry]自動重試MaxAttempts, DelayMs, IsExponentialBackoffEnabled
[CircuitBreaker(FailureThreshold = 5, BreakDurationMs = 30000)]
[Retry(MaxAttempts = 3, IsExponentialBackoffEnabled = true)]
public async Task<Data> FetchFromExternalApiAsync()
{
// 5 次失敗後熔斷 30 秒
// 重試 3 次,延遲 500ms → 1000ms → 2000ms
}

4. Caching 切面

切面用途參數
[Cache]快取返回值AbsoluteExpirationSeconds, SlidingExpirationSeconds
[CacheInvalidate]清除快取MethodNames, Pattern
[Cache(AbsoluteExpirationSeconds = 300)]
public async Task<Product> GetProductAsync(int id)
{
// 結果快取 5 分鐘
}

[CacheInvalidate(MethodNames = new[] { "GetProduct", "GetAllProducts" })]
public async Task UpdateProductAsync(Product product)
{
// 更新後自動清除相關快取
}

5. Validation 切面

切面用途
[NotNull]參數/屬性非 null
[NotEmpty]字串/集合非空
[Range]數值範圍驗證
public void ProcessUser(
[NotNull] User user,
[NotEmpty] string username,
[Range(1, 120)] int age)
{
// 參數自動驗證,失敗拋出 ArgumentException
}

6. Threading 切面

切面用途參數
[RunOnUiThread]強制 UI 執行緒-
[Synchronized]互斥鎖TimeoutMs, IsPerInstance
[RunOnUiThread]
public void UpdateStatusBar(string message)
{
// 自動切換到 UI 執行緒
}

[Synchronized(TimeoutMs = 5000)]
public void UpdateSharedResource()
{
// 自動互斥鎖,5 秒超時
}

7. Performance 切面

切面用途參數
[Throttle]節流IntervalMs, ReturnLastResult
[Debounce]防抖DelayMs
[Throttle(IntervalMs = 1000)]
public void UpdateUI()
{
// 最多 1 秒執行一次
}

[Debounce(DelayMs = 300)]
public async Task SearchAsync(string query)
{
// 300ms 無新呼叫後才執行
}

應用層必須使用切面的場景

強制使用(必須遵循)

場景必須使用的切面說明
Public 方法[Log]所有 public 方法必須有 logging
外部 API 呼叫[CircuitBreaker] + [Retry]防止外部服務故障影響系統
硬體通訊[CircuitBreaker] + [Retry]設備通訊可能失敗
FDA 相關操作[Part11Audit]合規性要求
資料修改[AuditDataChange]追蹤變更歷史
UI 更新[RunOnUiThread]確保 UI 執行緒安全
快取資料[Cache] / [CacheInvalidate]統一快取管理

範例:Service 類別

public class RecipeService : IRecipeService
{
[Log]
[Cache(AbsoluteExpirationSeconds = 300)]
public async Task<Recipe?> GetRecipeAsync(int id)
{
return await _repository.GetByIdAsync(id);
}

[Log]
[Part11Audit(EntityType = "Recipe", RequireChangeReason = true)]
[AuditDataChange(EntityType = "Recipe")]
[CacheInvalidate(MethodNames = new[] { "GetRecipe", "GetAllRecipes" })]
public async Task UpdateRecipeAsync(Recipe recipe, string changeReason)
{
await _repository.UpdateAsync(recipe);
}
}

範例:外部通訊

public class ExternalApiClient
{
[Log]
[CircuitBreaker(FailureThreshold = 5, BreakDurationMs = 30000)]
[Retry(MaxAttempts = 3, IsExponentialBackoffEnabled = true)]
public async Task<ApiResponse> CallExternalServiceAsync(ApiRequest request)
{
return await _httpClient.PostAsync(request);
}
}

範例:ViewModel

[NotifyPropertyChanged]
public partial class RecipeEditorViewModel : ViewModelBase
{
public string RecipeName { get; set; }

[Log]
[RunOnUiThread]
public async Task LoadRecipeAsync(int id)
{
var recipe = await _recipeService.GetRecipeAsync(id);
RecipeName = recipe?.Name ?? string.Empty;
}
}

允許直接 Logging 的場景

以下情況可以直接使用 _logger.Log*()

場景說明範例
底層框架內部GST.Core.* 專案框架需要細粒度控制
迴圈內部詳細記錄每次迭代的細節_logger.Debug("Processing item {i}", i)
條件式記錄根據條件決定是否記錄if (isVerbose) _logger.Debug(...)
Private 方法內部非入口點的內部邏輯內部輔助方法

注意事項

  • 直接 logging 時必須在程式碼註解中說明原因
  • 優先考慮是否可以用切面取代
// Allowed: Loop iteration details cannot be captured by aspect
for (var i = 0; i < items.Count; i++)
{
_logger.Debug("Processing item {Index} of {Total}", i, items.Count);
ProcessItem(items[i]);
}

禁止事項

應用層禁止

禁止事項說明
Console.WriteLine()絕對禁止,使用 logging
Debug.WriteLine()絕對禁止,使用 logging
未包裝的 _logger.Log*() 在 public 方法必須使用 [Log] 切面
手動 try-catch 僅為 logging使用 [LogException][HandleException]

錯誤範例

// Public method WITHOUT [Log] aspect
public async Task<Recipe> GetRecipeAsync(int id)
{
_logger.LogInformation("Getting recipe {Id}", id); // 直接 logging
try
{
return await _repository.GetByIdAsync(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get recipe"); // 手動 catch 僅為 logging
throw;
}
}

正確範例

// Public method WITH [Log] aspect
[Log(LogParameters = true, LogExceptions = true)]
public async Task<Recipe> GetRecipeAsync(int id)
{
return await _repository.GetByIdAsync(id);
// 切面自動處理:入口 logging、參數記錄、例外記錄
}

切面初始化

應用程式啟動時必須初始化切面服務

// Program.cs 或 App.xaml.cs
var services = new ServiceCollection();

// 註冊 GST 服務
services.AddGstLogging(config);
services.AddGstAspects();

// 建立 ServiceProvider
var provider = services.BuildServiceProvider();

// 初始化切面服務定位器(必須!)
provider.InitializeAspects();

未初始化的後果

如果未呼叫 InitializeAspects()

  • Logging 切面無法正常運作
  • Cache 切面無法存取快取服務
  • Audit 切面無法取得當前用戶資訊
  • 應用程式可能拋出 InvalidOperationException

常見組合模式

模式 1:完整的 CRUD Service

public class EntityService<T> : IEntityService<T>
{
[Log]
[Cache(AbsoluteExpirationSeconds = 300)]
public async Task<T?> GetByIdAsync(int id) { ... }

[Log]
[Cache(AbsoluteExpirationSeconds = 60)]
public async Task<IReadOnlyList<T>> GetAllAsync() { ... }

[Log]
[Part11Audit(EntityType = nameof(T))]
[CacheInvalidate(MethodNames = new[] { "GetById", "GetAll" })]
public async Task CreateAsync(T entity) { ... }

[Log]
[Part11Audit(EntityType = nameof(T), RequireChangeReason = true)]
[AuditDataChange(EntityType = nameof(T))]
[CacheInvalidate(MethodNames = new[] { "GetById", "GetAll" })]
public async Task UpdateAsync(T entity, string changeReason) { ... }

[Log]
[Part11Audit(EntityType = nameof(T), RequireChangeReason = true)]
[CacheInvalidate(MethodNames = new[] { "GetById", "GetAll" })]
public async Task DeleteAsync(int id, string changeReason) { ... }
}

模式 2:外部 API 整合

public class ExternalServiceClient
{
[Log]
[CircuitBreaker(FailureThreshold = 5, BreakDurationMs = 30000)]
[Retry(MaxAttempts = 3, IsExponentialBackoffEnabled = true)]
[Cache(AbsoluteExpirationSeconds = 60)]
public async Task<ExternalData> FetchDataAsync(string id) { ... }

[Log]
[CircuitBreaker(FailureThreshold = 3, BreakDurationMs = 60000)]
[Retry(MaxAttempts = 2)]
public async Task SendDataAsync(ExternalData data) { ... }
}

模式 3:硬體通訊

public class DeviceController
{
[Log]
[CircuitBreaker(FailureThreshold = 3, BreakDurationMs = 10000)]
[Retry(MaxAttempts = 3, DelayMs = 500)]
[Synchronized]
public async Task<DeviceStatus> GetStatusAsync() { ... }

[Log]
[CircuitBreaker(FailureThreshold = 3, BreakDurationMs = 10000)]
[Retry(MaxAttempts = 2)]
[Synchronized]
public async Task SendCommandAsync(DeviceCommand command) { ... }
}

模式 4:UI ViewModel

[NotifyPropertyChanged]
public partial class DataViewModel : ViewModelBase
{
public string Status { get; set; }
public bool IsLoading { get; set; }

[Log]
[RunOnUiThread]
public async Task LoadDataAsync()
{
IsLoading = true;
try
{
var data = await _service.GetDataAsync();
Status = data.Status;
}
finally
{
IsLoading = false;
}
}

[Log]
[RunOnUiThread]
[Throttle(IntervalMs = 500)]
public void RefreshUI()
{
// UI 更新邏輯
}
}

檢查清單

開發完成後,請確認:

  • 所有 public 方法都有 [Log] 切面
  • 外部通訊有 [CircuitBreaker] + [Retry]
  • FDA 相關操作有 [Part11Audit]
  • 資料修改有 [AuditDataChange]
  • Console.WriteLineDebug.WriteLine
  • 應用程式啟動時有呼叫 InitializeAspects()
  • 直接 logging(如有)已在註解中說明原因

相關文件

文件完整路徑
切面原始碼D:\WorkSpace\GatherTech\Core\GST-develop\src\Core\GST.Core.Aspects\
切面測試D:\WorkSpace\GatherTech\Core\GST-develop\tests\GST.Core.Aspects.Tests\
共用規範D:\WorkSpace\GatherTech\PM\docs\shared\agent-rules\COMMON-RULES.md
命名規範D:\WorkSpace\GatherTech\PM\docs\shared\GST-CSharp-Naming-Conventions.md

版本記錄

版本日期變更
1.0.02026-02-03初始版本