Skip to main content

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

ProjectNamespaceNuGet
GST.Core.AspectsGST.Core.Aspects.*Internal package

Namespace Reference

NamespaceAspect 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

AspectPurposeParameters
[Log]Log method entry/exitLogParameters, LogReturnValue, LogExceptions
[LogPerformance]Log execution timeThresholdMs (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)

AspectPurposeParameters
[Audit]Basic auditOperationName, IncludeParameters
[Part11Audit]FDA compliant auditEntityType, RequireChangeReason
[AuditDataChange]Data change trackingEntityType, 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

AspectPurposeParameters
[HandleException]Catch and handle exceptionsSuppress
[CircuitBreaker]Circuit breaker patternFailureThreshold, BreakDurationMs
[Retry]Automatic retryMaxAttempts, 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

AspectPurposeParameters
[Cache]Cache return valuesAbsoluteExpirationSeconds, SlidingExpirationSeconds
[CacheInvalidate]Clear cacheMethodNames, 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

AspectPurpose
[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

AspectPurposeParameters
[RunOnUiThread]Force UI thread execution-
[Synchronized]Mutex lockTimeoutMs, 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

AspectPurposeParameters
[Throttle]ThrottlingIntervalMs, ReturnLastResult
[Debounce]DebouncingDelayMs
[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)

ScenarioRequired AspectsDescription
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:

ScenarioDescriptionExample
Internal framework codeGST.Core.* projectsFramework needs fine-grained control
Detailed loop loggingDetails for each iteration_logger.Debug("Processing item {i}", i)
Conditional loggingLog based on conditionsif (isVerbose) _logger.Debug(...)
Inside private methodsInternal logic, not entry pointsInternal 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

ProhibitionDescription
Console.WriteLine()Absolutely prohibited, use logging
Debug.WriteLine()Absolutely prohibited, use logging
Unwrapped _logger.Log*() in public methodsMust use [Log] aspect
Manual try-catch solely for loggingUse [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.WriteLine or Debug.WriteLine
  • InitializeAspects() is called at application startup
  • Direct logging (if any) has a reason explained in comments

DocumentFull Path
Aspect source codeD:\WorkSpace\GatherTech\Core\GST-develop\src\Core\GST.Core.Aspects\
Aspect testsD:\WorkSpace\GatherTech\Core\GST-develop\tests\GST.Core.Aspects.Tests\
Shared standardsD:\WorkSpace\GatherTech\PM\docs\shared\agent-rules\COMMON-RULES.md
Naming conventionsD:\WorkSpace\GatherTech\PM\docs\shared\GST-CSharp-Naming-Conventions.md

Version History

VersionDateChanges
1.0.02026-02-03Initial version