GST Aspect Usage Standards
Scope: All application-layer projects using the GST base framework
Last Updated: 2026-02-03
Overview
The GST base framework provides a complete set of Metalama aspect designs (GST.Core.Aspects). Application-layer projects must use these aspects correctly to ensure:
- Unified logging format and behavior
- FDA Part 11 compliance (audit trail)
- Consistent exception handling and retry mechanisms
- Maintainable cross-cutting concern management
Aspect Locations
| Project | Namespace | NuGet |
|---|---|---|
GST.Core.Aspects | GST.Core.Aspects.* | Internal package |
Namespace Reference
| Namespace | Aspect Classes |
|---|---|
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] |
Available Aspects
1. Logging Aspects
| Aspect | Purpose | Parameters |
|---|---|---|
[Log] | Log method entry/exit | LogParameters, LogReturnValue, LogExceptions |
[LogPerformance] | Log execution time | ThresholdMs (logs only when exceeded) |
[LogException] | Log exception information | - |
[Log(LogParameters = true, LogReturnValue = true)]
public async Task<Recipe> GetRecipeAsync(int id)
{
// Automatically logs: method entry, parameters, return value, exceptions
}
[LogPerformance(ThresholdMs = 100)]
public void ExpensiveOperation()
{
// Only logs when execution exceeds 100ms
}
2. Audit Aspects (FDA Part 11)
| Aspect | Purpose | Parameters |
|---|---|---|
[Audit] | Basic audit | OperationName, IncludeParameters |
[Part11Audit] | FDA compliant audit | EntityType, RequireChangeReason |
[AuditDataChange] | Data change tracking | EntityType, OldValueParameter |
[RequireChangeReason] | Require change reason | - |
[Part11Audit(EntityType = "Recipe", RequireChangeReason = true)]
public async Task UpdateRecipeAsync(int id, Recipe recipe, string changeReason)
{
// Automatically logs: User, timestamp, Hash Chain, integrity verification
}
[AuditDataChange(EntityType = "Recipe", OldValueParameter = "existing")]
public void Update(Recipe existing, Recipe newValues)
{
// Automatically logs before and after values
}
3. Exception Aspects
| Aspect | Purpose | Parameters |
|---|---|---|
[HandleException] | Catch and handle exceptions | Suppress |
[CircuitBreaker] | Circuit breaker pattern | FailureThreshold, BreakDurationMs |
[Retry] | Automatic retry | MaxAttempts, DelayMs, IsExponentialBackoffEnabled |
[CircuitBreaker(FailureThreshold = 5, BreakDurationMs = 30000)]
[Retry(MaxAttempts = 3, IsExponentialBackoffEnabled = true)]
public async Task<Data> FetchFromExternalApiAsync()
{
// Circuit breaks after 5 failures for 30 seconds
// Retries 3 times with delays of 500ms → 1000ms → 2000ms
}
4. Caching Aspects
| Aspect | Purpose | Parameters |
|---|---|---|
[Cache] | Cache return values | AbsoluteExpirationSeconds, SlidingExpirationSeconds |
[CacheInvalidate] | Clear cache | MethodNames, Pattern |
[Cache(AbsoluteExpirationSeconds = 300)]
public async Task<Product> GetProductAsync(int id)
{
// Result cached for 5 minutes
}
[CacheInvalidate(MethodNames = new[] { "GetProduct", "GetAllProducts" })]
public async Task UpdateProductAsync(Product product)
{
// Automatically clears related cache after update
}
5. Validation Aspects
| Aspect | Purpose |
|---|---|
[NotNull] | Parameter/property must not be null |
[NotEmpty] | String/collection must not be empty |
[Range] | Numeric range validation |
public void ProcessUser(
[NotNull] User user,
[NotEmpty] string username,
[Range(1, 120)] int age)
{
// Parameters are automatically validated; failure throws ArgumentException
}
6. Threading Aspects
| Aspect | Purpose | Parameters |
|---|---|---|
[RunOnUiThread] | Force UI thread execution | - |
[Synchronized] | Mutex lock | TimeoutMs, IsPerInstance |
[RunOnUiThread]
public void UpdateStatusBar(string message)
{
// Automatically switches to UI thread
}
[Synchronized(TimeoutMs = 5000)]
public void UpdateSharedResource()
{
// Automatic mutex lock with 5-second timeout
}
7. Performance Aspects
| Aspect | Purpose | Parameters |
|---|---|---|
[Throttle] | Throttling | IntervalMs, ReturnLastResult |
[Debounce] | Debouncing | DelayMs |
[Throttle(IntervalMs = 1000)]
public void UpdateUI()
{
// Executes at most once per second
}
[Debounce(DelayMs = 300)]
public async Task SearchAsync(string query)
{
// Executes only after 300ms without new calls
}
Scenarios Requiring Aspects in Application Layer
Mandatory Usage (Must Follow)
| Scenario | Required Aspects | Description |
|---|---|---|
| Public methods | [Log] | All public methods must have logging |
| External API calls | [CircuitBreaker] + [Retry] | Prevent external service failures from affecting the system |
| Hardware communication | [CircuitBreaker] + [Retry] | Device communication may fail |
| FDA-related operations | [Part11Audit] | Compliance requirement |
| Data modifications | [AuditDataChange] | Track change history |
| UI updates | [RunOnUiThread] | Ensure UI thread safety |
| Cached data | [Cache] / [CacheInvalidate] | Unified cache management |
Example: Service Class
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);
}
}
Example: External Communication
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);
}
}
Example: 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;
}
}
Scenarios Allowing Direct Logging
The following situations may use _logger.Log*() directly:
| Scenario | Description | Example |
|---|---|---|
| Internal framework code | GST.Core.* projects | Framework needs fine-grained control |
| Detailed loop logging | Details for each iteration | _logger.Debug("Processing item {i}", i) |
| Conditional logging | Log based on conditions | if (isVerbose) _logger.Debug(...) |
| Inside private methods | Internal logic, not entry points | Internal helper methods |
Notes
- When using direct logging, you must explain the reason in a code comment
- First consider whether an aspect can replace the direct 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]);
}
Prohibited Practices
Application Layer Prohibitions
| Prohibition | Description |
|---|---|
Console.WriteLine() | Absolutely prohibited, use logging |
Debug.WriteLine() | Absolutely prohibited, use logging |
Unwrapped _logger.Log*() in public methods | Must use [Log] aspect |
| Manual try-catch solely for logging | Use [LogException] or [HandleException] |
Incorrect Example
// Public method WITHOUT [Log] aspect
public async Task<Recipe> GetRecipeAsync(int id)
{
_logger.LogInformation("Getting recipe {Id}", id); // Direct logging
try
{
return await _repository.GetByIdAsync(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get recipe"); // Manual catch solely for logging
throw;
}
}
Correct Example
// Public method WITH [Log] aspect
[Log(LogParameters = true, LogExceptions = true)]
public async Task<Recipe> GetRecipeAsync(int id)
{
return await _repository.GetByIdAsync(id);
// Aspect automatically handles: entry logging, parameter recording, exception logging
}
Aspect Initialization
Aspect services must be initialized at application startup
// Program.cs or App.xaml.cs
var services = new ServiceCollection();
// Register GST services
services.AddGstLogging(config);
services.AddGstAspects();
// Build ServiceProvider
var provider = services.BuildServiceProvider();
// Initialize aspect service locator (required!)
provider.InitializeAspects();
Consequences of Not Initializing
If InitializeAspects() is not called:
- Logging aspects will not function properly
- Cache aspects cannot access cache services
- Audit aspects cannot retrieve current user information
- Application may throw
InvalidOperationException
Common Combination Patterns
Pattern 1: Complete 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) { ... }
}
Pattern 2: External API Integration
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) { ... }
}
Pattern 3: Hardware Communication
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) { ... }
}
Pattern 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 update logic
}
}
Checklist
After development is complete, verify the following:
- All public methods have the
[Log]aspect - External communication has
[CircuitBreaker]+[Retry] - FDA-related operations have
[Part11Audit] - Data modifications have
[AuditDataChange] - No
Console.WriteLineorDebug.WriteLine -
InitializeAspects()is called at application startup - Direct logging (if any) has a reason explained in comments
Related Documents
| Document | Full Path |
|---|---|
| Aspect source code | D:\WorkSpace\GatherTech\Core\GST-develop\src\Core\GST.Core.Aspects\ |
| Aspect tests | D:\WorkSpace\GatherTech\Core\GST-develop\tests\GST.Core.Aspects.Tests\ |
| Shared standards | D:\WorkSpace\GatherTech\PM\docs\shared\agent-rules\COMMON-RULES.md |
| Naming conventions | D:\WorkSpace\GatherTech\PM\docs\shared\GST-CSharp-Naming-Conventions.md |
Version History
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2026-02-03 | Initial version |