模式函式庫
Metalama 提供官方模式函式庫 — 針對常見橫切關注點的正式可用 Aspect。這些以獨立的 NuGet 套件提供,並提供經過充分測試的實作,你可以直接使用或從中學習。
總覽
| 套件 | 用途 | 主要 Aspect |
|---|---|---|
Metalama.Patterns.Contracts | 輸入驗證 | [NotNull], [NotEmpty], [Range], [Positive], [Regex], [Url], [Email] |
Metalama.Patterns.Caching | 方法結果快取 | [Cache], [InvalidateCache] |
Metalama.Patterns.Observability | MVVM 屬性通知 | [Observable] |
Metalama.Patterns.Memoization | 純函式記憶化 | [Memoize] |
注意:GST 框架在
GST.Core.Aspects中實作了自己的 Aspect,而非使用這些官方套件。這是因為 GST 需要自訂整合(AspectServiceLocator、ILoggerService、FDA 合規性)。然而,了解官方模式對於學習最佳實踐仍然很有價值。
Metalama.Patterns.Contracts
目的
基於 Contract 的程式設計:對參數強制執行前置條件、對回傳值強制執行後置條件、對屬性強制執行不變式。
安裝
dotnet add package Metalama.Patterns.Contracts
可用的 Contract
| Contract | 驗證內容 | 範例 |
|---|---|---|
[NotNull] | 值不為 null | void Process([NotNull] string input) |
[NotEmpty] | 字串/集合不為 null 或空 | void Save([NotEmpty] string name) |
[Required] | 值不為 null/空/空白 | [Required] string Title { get; set; } |
[Range(min, max)] | 數值在範圍內 | void SetTemp([Range(0, 100)] int temp) |
[Positive] | 值 > 0 | void Resize([Positive] int width) |
[StrictlyPositive] | 值 > 0(與 Positive 相同) | [Positive] 的別名 |
[NonNegative] | 值 >= 0 | void SetCount([NonNegative] int count) |
[StrictlyNegative] | 值 < 0 | void SetOffset([StrictlyNegative] int offset) |
[Regex(pattern)] | 字串符合正規表示式 | [Regex(@"^\d{3}$")] string Code |
[Url] | 有效的 URL 格式 | [Url] string Endpoint { get; set; } |
[Email] | 有效的電子郵件格式 | [Email] string Contact { get; set; } |
[Phone] | 有效的電話格式 | [Phone] string PhoneNumber { get; set; } |
[CreditCard] | 有效的信用卡號碼 | [CreditCard] string CardNumber |
[EnumDataType] | 有效的列舉值 | [EnumDataType] MyEnum Value |
[StringLength(min, max)] | 字串長度在範圍內 | [StringLength(1, 50)] string Name |
使用模式
參數驗證(前置條件)
using Metalama.Patterns.Contracts;
public class OrderService
{
public Order CreateOrder(
[NotNull] Customer customer,
[NotEmpty] List<OrderItem> items,
[Range(0.01, 999999.99)] decimal total)
{
// All parameters are validated before this line executes
// If validation fails, an appropriate exception is thrown
return new Order(customer, items, total);
}
}
屬性驗證(不變式)
public class Configuration
{
[Range(1, 65535)]
public int Port { get; set; }
[NotEmpty]
public string Host { get; set; }
[Url]
public string Endpoint { get; set; }
}
回傳值驗證(後置條件)
public class UserRepository
{
[return: NotNull]
public User GetById(int id)
{
var user = _db.Users.Find(id);
return user; // Throws if result is null
}
}
相較於手動驗證的優勢
| 手動方式 | 基於 Contract 的方式 |
|---|---|
if (name == null) throw new ArgumentNullException(nameof(name)); | [NotNull] string name |
| 容易遺忘 | 不可能遺忘(宣告式套用) |
| 不一致的錯誤訊息 | 標準化、一致的訊息 |
| 不會被繼承 | Contract 可從介面繼承 |
| 冗長 | 簡潔 |
Contract 繼承
套用在介面方法上的 Contract 會自動被實作類別繼承:
public interface IRepository<T>
{
T GetById([Positive] int id);
void Save([NotNull] T entity);
}
public class OrderRepository : IRepository<Order>
{
// [Positive] and [NotNull] are automatically enforced here!
public Order GetById(int id) { ... }
public void Save(Order entity) { ... }
}
Metalama.Patterns.Caching
目的
根據方法參數自動快取回傳值。提供快取失效機制以保持快取資料的即時性。
安裝
dotnet add package Metalama.Patterns.Caching
基本快取
using Metalama.Patterns.Caching.Aspects;
public class ProductService
{
[Cache]
public Product GetProduct(int id)
{
// This will only execute once per unique 'id'
// Subsequent calls return the cached result
return _repository.FindById(id);
}
[Cache]
public async Task<List<Product>> GetProductsByCategoryAsync(string category)
{
// Async methods are fully supported
return await _repository.FindByCategoryAsync(category);
}
}
快取失效
public class ProductService
{
[Cache]
public Product GetProduct(int id) => _repository.FindById(id);
[InvalidateCache(nameof(GetProduct))]
public void UpdateProduct(int id, Product product)
{
_repository.Update(product);
// Cache for GetProduct(id) is automatically invalidated
}
[InvalidateCache(nameof(GetProduct))]
public void DeleteProduct(int id)
{
_repository.Delete(id);
// Cache for GetProduct(id) is automatically invalidated
}
}
快取鍵產生
Metalama 會自動從以下項目產生快取鍵:
- 方法名稱(完整限定名稱)
- 所有參數值(使用
ToString()或自訂ICacheKey)
對於作為參數的自訂物件,需實作 ICacheKey:
public class ProductFilter : ICacheKey
{
public string Category { get; set; }
public decimal? MinPrice { get; set; }
public string ToCacheKey() => $"{Category}_{MinPrice}";
}
快取後端
| 後端 | 套件 | 使用情境 |
|---|---|---|
| 記憶體內 | 內建(MemoryCache) | 單一伺服器應用程式 |
| Redis | Metalama.Patterns.Caching.Backends.Redis | 分散式、多伺服器 |
| L1+L2 | 分層設定 | 本機記憶體 + Redis 備援 |
設定
// In Startup.cs / Program.cs
services.AddMetalamaCaching(options =>
{
options.DefaultExpiration = TimeSpan.FromMinutes(10);
options.Profiles.Add("short", new CachingProfile
{
AbsoluteExpiration = TimeSpan.FromMinutes(1)
});
options.Profiles.Add("long", new CachingProfile
{
AbsoluteExpiration = TimeSpan.FromHours(1)
});
});
[Cache(ProfileName = "short")]
public Product GetProduct(int id) { ... }
[Cache(ProfileName = "long")]
public List<Category> GetCategories() { ... }
Metalama.Patterns.Observability
目的
自動實作 INotifyPropertyChanged,適用於 WPF/MVVM 的 ViewModel,消除樣板屬性通知程式碼。
安裝
dotnet add package Metalama.Patterns.Observability
基本用法
using Metalama.Patterns.Observability;
[Observable]
public partial class PersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
}
重要:類別必須是
partial。
產生的內容
[Observable] Attribute 會自動:
- 實作
INotifyPropertyChanged介面 - 加入
PropertyChanged事件 - 在每個屬性 setter 中加入變更通知
- 偵測計算屬性的相依性(
FullName相依於FirstName和LastName) - 當
FirstName或LastName變更時通知FullName
// Conceptual generated code:
public partial class PersonViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string _firstName;
public string FirstName
{
get => _firstName;
set
{
if (!Equals(_firstName, value))
{
_firstName = value;
OnPropertyChanged(nameof(FirstName));
OnPropertyChanged(nameof(FullName)); // Dependency detected!
}
}
}
// ...similar for LastName
}
處理複雜相依性
子物件相依性
[Observable]
public partial class OrderViewModel
{
public Customer Customer { get; set; }
// Depends on Customer.Name — if Customer implements INPC,
// OrderViewModel will forward the notification
public string CustomerName => Customer?.Name ?? "Unknown";
}
方法相依性
[Observable]
public partial class CalculatorViewModel
{
public double Price { get; set; }
public int Quantity { get; set; }
// Metalama analyzes GetTotal() and detects it reads Price and Quantity
public double Total => GetTotal();
private double GetTotal() => Price * Quantity;
}
不支援的模式
某些模式無法被分析。Metalama 會回報警告(LAMA51xx):
| 模式 | 問題 |
|---|---|
| 外部方法呼叫 | 無法追蹤透過外部方法的相依性 |
| 複雜的 LINQ 運算式 | 無法靜態分析所有 LINQ 操作 |
| 基於反射的存取 | 無法在編譯期追蹤 |
Metalama.Patterns.Memoization
目的
快取純函式、無參數屬性或方法的結果。比完整快取更簡單 — 值只計算一次,並在物件生命週期內儲存。
用法
using Metalama.Patterns.Memoization;
public partial class ExpensiveComputation
{
[Memoize]
public double Result => ComputeExpensiveResult();
private double ComputeExpensiveResult()
{
// This only runs once, result is cached in a backing field
Thread.Sleep(5000);
return Math.PI * Math.E;
}
}
Memoize 與 Cache 的比較
| 特性 | [Memoize] | [Cache] |
|---|---|---|
| 參數 | 無(僅限無參數) | 任意 |
| 儲存 | 實例欄位 | 外部快取服務 |
| 生命週期 | 物件生命週期 | 可設定的過期時間 |
| 失效 | 無(不可變) | [InvalidateCache] |
| 使用情境 | 純計算屬性 | 服務方法結果 |
GST 與官方模式的比較
GST 框架有自己的實作,而非使用官方套件:
| 功能 | 官方套件 | GST 實作 | 自訂原因 |
|---|---|---|---|
| 驗證 | Metalama.Patterns.Contracts | GST.Core.Aspects.Validation | 自訂錯誤處理整合 |
| 快取 | Metalama.Patterns.Caching | GST.Core.Aspects.Caching | 自訂 ICacheService 抽象層 |
| 可觀察性 | Metalama.Patterns.Observability | GST.Core.Aspects.Observability | 自訂日誌記錄 + DependsOn |
| 日誌記錄 | 無(未提供) | GST.Core.Aspects.Logging | 核心需求,自訂 ILoggerService |
| 重試 | 無 | GST.Core.Aspects.Exception | 領域特定(SECS/GEM、Modbus) |
| 稽核 | 無 | GST.Core.Aspects.Audit | FDA 21 CFR Part 11 合規性 |
| 授權 | 無 | GST.Core.Aspects.Authorization | 自訂權限模型 |
了解官方模式能幫助你:
- 從框架作者身上學習 Metalama 最佳實踐
- 在建構自訂 Aspect 時比較不同做法
- 在不需要 GST 自訂整合的專案中使用官方套件
下一步:GST 實際範例 — GST 框架如何在生產環境中使用 Metalama。