Advanced Topics
This guide covers advanced shorthand patterns, debugging strategies, performance considerations, and integration with the broader expression system. Recommended for architects and framework developers.
Prefix Strategies and Naming Conventions
Single-Prefix Strategy
All shorthands use a consistent primary prefix style:
// Strategy: Use only "$" prefix family
$output // Node outputs
$input // Node inputs
$var // Variables
$memory // Execution memory
$config // Custom config
$user // Custom user data
// Pros:
// - Consistent, predictable
// - Easy to remember
// - Simple pattern matching
// Cons:
// - Limited to one prefix style
// - May conflict with framework conventions
// - Hard to distinguish categories visually
Mixed-Prefix Strategy
Different prefixes for different categories:
// Strategy: Multiple semantic prefixes
$output // Node outputs ($ = built-in)
$$parsed // Parsed data (double $)
@selector // DOM selectors (@)
@api // API references (@)
#feature // Feature flags (#)
// Pros:
// - Visual distinction between categories
// - Can encode type information in prefix
// - Semantic clarity
// Cons:
// - More complex to manage
// - Harder to document
// - Users need to learn multiple conventions
Hierarchical Strategy
Nested prefixes for complex data hierarchies:
// Strategy: Namespace-like prefixes
$output.rest // REST API outputs
$output.database // Database outputs
$output.webhook // Webhook outputs
$memory.cache // Cache memory
$memory.persistent // Persistent memory
// Pros:
// - Scales to large numbers of shorthands
// - Groups related shorthands
// - Reflects data source hierarchy
// Cons:
// - More verbose syntax
// - Implementation complexity
// - Steeper learning curve
Shorthand Resolution Process
Understanding how shorthands are resolved helps with debugging and optimization:
Step 1: Parse Expression
// Input from workflow designer
@{$output:form-1.email}
// Parser extracts:
// - Sigil: @{...}
// - Prefix: $output
// - Path: form-1.email
Step 2: Prefix Matching
// Registry lookup
ShorthandRegistry.GetDefinition("$output") → ShorthandDefinition found
// If not found:
ShorthandRegistry.GetDefinition("$output") → null (treated as directive)
Step 3: Expansion
// Shorthand expands the path
$output.Expand("form-1.email")
→ "@{output:form-1.email}"
Step 4: Evaluation
// Expanded directive is evaluated normally
@{output:form-1.email} → alice@example.com
Performance Considerations
Shorthand Resolution Overhead
Shorthands add minimal overhead but should be considered at scale:
| Operation | Cost | Optimization |
|---|---|---|
| Prefix matching | O(n) where n = registered shorthands | Use HashMap for O(1) lookup |
| Expansion | O(1) string concatenation | Cache expansion results if repeated |
| Directive evaluation | O(m) where m = resolution steps | Depends on directive type, not shorthand |
Optimization Patterns
// Pattern 1: Cache frequently expanded shorthands
public class CachingShorthandDefinition : IShorthandDefinition
{
private readonly Dictionary<string, string> _cache = new();
private readonly IShorthandDefinition _inner;
public string Prefix => _inner.Prefix;
public string Expand(string shorthandPath)
{
if (_cache.TryGetValue(shorthandPath, out var cached))
return cached;
var result = _inner.Expand(shorthandPath);
_cache[shorthandPath] = result;
return result;
}
}
// Pattern 2: Lazy-load shorthands on demand
public class LazyShorthandRegistry
{
private readonly Lazy<Dictionary<string, IShorthandDefinition>> _definitions;
public IShorthandDefinition GetDefinition(string prefix)
{
var definitions = _definitions.Value; // Only loaded on first access
return definitions.TryGetValue(prefix, out var def) ? def : null;
}
}
Debugging Shorthands
Enable Expression Logging
// Log shorthand expansions during evaluation
public class DebuggingExpressionResolver : IExpressionResolver
{
private readonly ILogger<DebuggingExpressionResolver> _logger;
public async Task<object> ResolveAsync(string expression, EvaluationContext context)
{
_logger.LogInformation("Original expression: {Expression}", expression);
var expanded = ExpandShorthands(expression);
if (expanded != expression)
_logger.LogInformation("Expanded expression: {Expanded}", expanded);
var result = await _evaluator.EvaluateAsync(expanded, context);
_logger.LogInformation("Evaluation result: {Result}", result);
return result;
}
private string ExpandShorthands(string expression)
{
// Expansion logic with logging
return expanded;
}
}
// Configuration
builder.Services.AddScoped<IExpressionResolver>, DebuggingExpressionResolver>();
Test Shorthand Expansions
[TestClass]
public class ShorthandExpansionTests
{
private IShorthandDefinition _shorthand;
[TestInitialize]
public void Setup()
{
_shorthand = new ConfigShorthandDefinition();
}
[TestMethod]
public void Expand_SimpleKey_ReturnsValidDirective()
{
// Arrange
string path = "maxRetries";
// Act
string expanded = _shorthand.Expand(path);
// Assert
Assert.IsTrue(expanded.StartsWith("@{"));
Assert.IsTrue(expanded.EndsWith("}"));
Assert.AreEqual("@{memory:config.maxRetries}", expanded);
}
[TestMethod]
public void Expand_NestedPath_HandlesCorrectly()
{
// Arrange
string path = "database.connectionTimeout";
// Act
string expanded = _shorthand.Expand(path);
// Assert
Assert.AreEqual("@{memory:config.database.connectionTimeout}", expanded);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Expand_InvalidPath_ThrowsException()
{
// Arrange
string path = "../../etc/passwd"; // Path traversal attempt
// Act
_shorthand.Expand(path);
// Assert: Exception expected
}
}
Visual Debugging in Designer
If your designer supports expression preview, show expanded forms:
// In workflow designer UI
Expression Input: @{$config:maxRetries}
Expands to: @{memory:config.maxRetries}
Evaluates to: 3
Type: Integer
Integration with Directives
Shorthands with Modifiers
Some frameworks support modifiers on directives. Combine with shorthands carefully:
// Standard modifier usage
@{output:api-1.responseBody | parseJson}
// Using shorthand with modifier
// This requires the modifier to apply to the expanded form
@{$output:api-1.responseBody | parseJson}
// Implementation: Modifiers are applied AFTER expansion
1. Expand: @{$output:api-1.responseBody | parseJson}
→ @{output:api-1.responseBody | parseJson}
2. Evaluate: Apply parseJson modifier to result
Chaining Multiple Shorthands
Avoid nested shorthands — they don't compose:
// Bad: Nested shorthands (don't work)
@{$config:@{$memory:configKey}}
// Good: Single shorthand with complete path
@{$config:settingName}
// Good: Use JavaScript to combine
@{js: $config.setting1 + $config.setting2}
Common Patterns and Anti-Patterns
Good: Domain-Specific Shorthand
// Clear, focused purpose
$user // User data
$config // Configuration
$api // API responses
// Pros: Simple to understand and use
Bad: Too-Generic Shorthand
// Unclear what it references
$data
$info
$temp
// Cons: Leads to confusion and misuse
Good: Consistent Nesting
// All follow same pattern
$output.rest
$output.database
$output.webhook
$memory.cache
$memory.persistent
// Pros: Easy to predict and discover
Bad: Inconsistent Nesting
// Mixed patterns
$output
$$output.parsed
@api
#cache
// Cons: Users must remember each rule
Good: Expansion Transparency
// Expansion is predictable
$config.maxRetries → @{memory:config.maxRetries}
$user.email → @{memory:user.email}
// Pros: Users can understand what's happening
Bad: Hidden Complexity
// Expansion is magic
$data → @{memory:contextData.user.profile.email.value}
// Cons: Users can't predict or debug easily
Security Implications
Input Validation
Always validate shorthand paths to prevent injection:
public class SecureShorthandDefinition : IShorthandDefinition
{
public string Prefix => "$config";
public string Expand(string shorthandPath)
{
// Validate against whitelist
var validPaths = new[] { "maxRetries", "timeout", "retryPolicy" };
if (!validPaths.Contains(shorthandPath))
throw new ArgumentException($"Invalid path: {shorthandPath}");
return $"@{{memory:config.{shorthandPath}}}";
}
}
// Or use regex validation
private static readonly Regex ValidPathPattern = new(@"^[a-zA-Z0-9_.]+$");
public string Expand(string shorthandPath)
{
if (!ValidPathPattern.IsMatch(shorthandPath))
throw new ArgumentException($"Invalid path characters: {shorthandPath}");
return $"@{{memory:config.{shorthandPath}}}";
}
Access Control
Control who can use which shorthands:
public class AccessControlledShorthandDefinition : IShorthandDefinition
{
private readonly IAccessControl _access;
public string Prefix => "$sensitive";
public string Expand(string shorthandPath)
{
// Check caller permissions
if (!_access.HasRole("admin"))
throw new UnauthorizedAccessException(
"Only administrators can access $sensitive shorthand"
);
return $"@{{memory:sensitive.{shorthandPath}}}";
}
}
Future Extensibility
Plugin Architecture
// Design for extensibility via plugin system
public interface IShorthandPlugin
{
IEnumerable<IShorthandDefinition> GetShorthands();
string Name { get; }
string Version { get; }
}
public class WalmartPlugin : IShorthandPlugin
{
public string Name => "Walmart Enterprise Shorthands";
public string Version => "1.0.0";
public IEnumerable<IShorthandDefinition> GetShorthands()
{
yield return new WalmartStoreShorthand();
yield return new WalmartCustomerShorthand();
yield return new WalmartInventoryShorthand();
}
}
// Load plugins at startup
foreach (var plugin in LoadPlugins())
{
foreach (var shorthand in plugin.GetShorthands())
{
services.AddSingleton<IShorthandDefinition>(shorthand);
}
}
Configuration-Driven Shorthands
// Define shorthands in configuration
{
"expressions": {
"shorthands": [
{
"prefix": "$config",
"expansion": "@{memory:appConfig.{path}}",
"description": "Application configuration"
},
{
"prefix": "$user",
"expansion": "@{memory:currentUser.{path}}",
"description": "Current user data"
}
]
}
}
// Load at startup
public void LoadShorthandsFromConfig(IConfiguration config)
{
var shorthandConfigs = config.GetSection("expressions:shorthands")
.GetChildren();
foreach (var shConfig in shorthandConfigs)
{
var shorthand = new ConfigurableShorthand(
shConfig["prefix"],
shConfig["expansion"],
shConfig["description"]
);
services.AddSingleton<IShorthandDefinition>(shorthand);
}
}
Monitoring and Observability
Shorthand Usage Metrics
public class MetricsShorthandDefinition : IShorthandDefinition
{
private readonly IMetricsCollector _metrics;
public string Prefix => "$config";
public string Expand(string shorthandPath)
{
_metrics.IncrementCounter("shorthand.expansion", new[]
{
("prefix", Prefix),
("path", shorthandPath)
});
return $"@{{memory:config.{shorthandPath}}}";
}
}
// Query metrics
// Most used shorthands?
// Failing expansions?
// Performance trends?
Migration and Refactoring
Renaming a Shorthand
// Phase 1: Support both old and new names
services.AddSingleton<IShorthandDefinition>(new ConfigShorthandDefinition("$config"));
services.AddSingleton<IShorthandDefinition>(new ConfigShorthandDefinition("$cfg")); // Legacy
// Phase 2: Log deprecation warnings
public string Expand(string shorthandPath)
{
if (Prefix == "$cfg")
_logger.LogWarning("Deprecated shorthand $cfg used. Use $config instead.");
return $"@{{memory:config.{shorthandPath}}}";
}
// Phase 3: After migration period, remove old shorthand
Changing Expansion Target
// Before: $config points to memory
// After: $config points to persistent store
// Safe migration:
1. Add new prefix $config-v2 pointing to persistent store
2. In designer, allow both $config and $config-v2
3. Log warning when $config is used
4. Migrate workflows from $config to $config-v2
5. Remove $config after all migrations complete