跳至主要内容

模式函式庫

Metalama 提供官方模式函式庫 — 針對常見橫切關注點的正式可用 Aspect。這些以獨立的 NuGet 套件提供,並提供經過充分測試的實作,你可以直接使用或從中學習。


總覽

套件用途主要 Aspect
Metalama.Patterns.Contracts輸入驗證[NotNull], [NotEmpty], [Range], [Positive], [Regex], [Url], [Email]
Metalama.Patterns.Caching方法結果快取[Cache], [InvalidateCache]
Metalama.Patterns.ObservabilityMVVM 屬性通知[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]值不為 nullvoid 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]值 > 0void Resize([Positive] int width)
[StrictlyPositive]值 > 0(與 Positive 相同)[Positive] 的別名
[NonNegative]值 >= 0void SetCount([NonNegative] int count)
[StrictlyNegative]值 < 0void 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 會自動從以下項目產生快取鍵:

  1. 方法名稱(完整限定名稱)
  2. 所有參數值(使用 ToString() 或自訂 ICacheKey

對於作為參數的自訂物件,需實作 ICacheKey

public class ProductFilter : ICacheKey
{
public string Category { get; set; }
public decimal? MinPrice { get; set; }

public string ToCacheKey() => $"{Category}_{MinPrice}";
}

快取後端

後端套件使用情境
記憶體內內建(MemoryCache單一伺服器應用程式
RedisMetalama.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 會自動:

  1. 實作 INotifyPropertyChanged 介面
  2. 加入 PropertyChanged 事件
  3. 在每個屬性 setter 中加入變更通知
  4. 偵測計算屬性的相依性FullName 相依於 FirstNameLastName
  5. FirstNameLastName 變更時通知 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.ContractsGST.Core.Aspects.Validation自訂錯誤處理整合
快取Metalama.Patterns.CachingGST.Core.Aspects.Caching自訂 ICacheService 抽象層
可觀察性Metalama.Patterns.ObservabilityGST.Core.Aspects.Observability自訂日誌記錄 + DependsOn
日誌記錄無(未提供)GST.Core.Aspects.Logging核心需求,自訂 ILoggerService
重試GST.Core.Aspects.Exception領域特定(SECS/GEM、Modbus)
稽核GST.Core.Aspects.AuditFDA 21 CFR Part 11 合規性
授權GST.Core.Aspects.Authorization自訂權限模型

了解官方模式能幫助你:

  1. 從框架作者身上學習 Metalama 最佳實踐
  2. 在建構自訂 Aspect 時比較不同做法
  3. 在不需要 GST 自訂整合的專案中使用官方套件

下一步GST 實際範例 — GST 框架如何在生產環境中使用 Metalama。