Evolution & Versioning
How to version templates safely, add new engine providers, and extend the Multi-Query system without breaking existing callers.
Template Versioning Strategy
Because template catalogue codes are embedded in workflow definitions, expression directives, and external API calls, changing or removing a template code is a breaking change. Use the following strategy to evolve templates safely while maintaining backward compatibility:
- Assign version suffixes to catalogue codes for non-trivial changes:
PAYROLL_REPORTbecomesPAYROLL_REPORT_v2. The original code remains active and served to existing callers. - Use the
namefield in the template JSON for a human-readable label (e.g.,"name": "Payroll Department Summary v2"). This label appears in administrative listings without affecting the code-based lookup. - Track a
versioninteger inside the JSON object to support audit history and rollback identification. Increment it on every update. - Communicate a migration window to all callers before retiring an old version code. Set
IsActive = 0on the old code only after all callers have migrated to the new code. - Keep old template codes active in staging and production simultaneously during the migration window. The database overhead of an additional row is negligible.
// Template JSON with version tracking
{
"name": "Payroll Department Summary v2",
"version": 2,
"isTemplate": true,
"sql": "SELECT d.ID, d.DeptName, d.CostCentre FROM dbo.Departments d WHERE d.TenantID = @TenantID",
"collectionName": "departments",
"children": [ ... ]
}
Adding New SQL Engines
The Multi-Query system is engine-agnostic. The sqlserver engine is the built-in implementation, but additional engines can be registered by implementing ISqlDeriveEngine and registering the implementation in the DI container.
Create a class that implements ISqlDeriveEngine. The interface requires query execution logic appropriate for the target database platform (PostgreSQL, MySQL, SQLite, etc.) and a stable engine name string.
public class PostgreSqlDeriveEngine : ISqlDeriveEngine
{
public string EngineName => "postgresql";
public Task<MultiQueryResult> ExecuteAsync(
MultiQueryTemplate template,
MultiQueryExecutionContext context)
{
// Implement PostgreSQL-specific query tree execution
}
}
The EngineName property must return a stable, lowercase string that is unique across all registered engines. This string becomes the engine segment in the directive token ({{multi-query:postgresql.CODE}}) and in the REST path. Once published, changing this string is a breaking change.
Register the new engine as part of the IEnumerable<ISqlDeriveEngine> collection in the service registration. The platform resolves engines via enumeration — no central registry or switch statement needs to be modified.
// In your module registration or Startup.cs
services.AddSingleton<ISqlDeriveEngine, SqlServerDeriveEngine>();
services.AddSingleton<ISqlDeriveEngine, PostgreSqlDeriveEngine>();
Store template JSON in dbo.Shared_Configurations as usual. Reference the new engine in directives and REST calls using the EngineName you defined:
{{multi-query:postgresql.ORG_CHART_SUMMARY}}
GET /api/v1/expressions/multiquery/ORG_CHART_SUMMARY
(engine resolved from template metadata or directive)
Engine Resolution
When a directive or endpoint is evaluated, MultiQueryExpressionDirectiveService resolves the target engine using a simple linear enumeration:
// Engine resolution — simplified illustration
var engine = _engines.FirstOrDefault(e =>
string.Equals(e.EngineName, engineName, StringComparison.OrdinalIgnoreCase));
if (engine is null)
{
_logger.LogWarning(
"Multi-Query directive: no engine registered for '{EngineName}'. Returning empty string.",
engineName);
return string.Empty;
}
If no registered engine matches the name parsed from the directive, the service returns an empty string and writes a warning to the platform log. No exception propagates to the caller. This makes it safe to deploy a new engine incrementally — templates referencing the new engine name will silently return empty until the engine implementation is registered.
Schema Evolution
When an underlying SQL table is altered — a column is renamed, dropped, or has its data type changed — the template JSON stored in
dbo.Shared_Configurations is not validated against the live schema. The template continues to store successfully. The failure occurs at execution time when SQL Server rejects the query. This produces an HTTP 500 Internal Server Error on the API and an empty string from the expression directive. Review all active templates that reference affected tables before applying schema changes, and update the SQL in the template JSON as part of the schema migration transaction.
Backward Compatibility Rules
The following rules define the Multi-Query compatibility contract. Violating them constitutes a breaking change and requires a new template code (version suffix) to avoid disrupting existing callers.
- Never change the meaning of a catalogue code. A code is a stable identifier. Once callers depend on a code's output structure, changing the root query or child structure under the same code is a breaking change.
- Add new codes for breaking changes. Use a version suffix (
_v2,_v3) and deprecate the old code with a documented migration window. - Keep
isTemplate: trueonly on root records. This flag marks the root of the query tree. Child nodes must not carry this flag — it causes incorrect template resolution. - Do not rename
collectionNamewithout versioning the code. ThecollectionNamevalue is the JSON key that callers use to access the collection in the output. Renaming it silently breaks all downstream code that references the old key. - Adding new columns to a query is non-breaking — callers that do not reference the new column are unaffected.
- Removing or renaming a column from a query is breaking — version the code before making this change.
Future Directions
The following extensions are under consideration for future releases of the Multi-Query engine:
PostgreSQL Engine
A first-class ISqlDeriveEngine implementation for PostgreSQL, enabling multi-level query trees over PostgreSQL databases alongside the existing SQL Server engine. Would use Npgsql and support the same @parent token syntax.
Template Composition
Allow a template to inherit or embed another template using a "$ref": "PARENT_TEMPLATE_CODE" directive in the JSON. Composed templates would be resolved and merged at load time, reducing duplication across similar reporting structures.
AI-Generated Templates
A natural-language-to-template authoring assistant that accepts a description of the desired data hierarchy and generates a valid Multi-Query JSON template. Draft templates would be presented for human review before being stored in dbo.Shared_Configurations.
Template Test Runner
An administrative tool for executing a template against a known fixture dataset and asserting the output matches an expected JSON snapshot. Enables regression testing of templates before promotion to production, preventing silent schema drift failures.
Changelog
| Version | Date | Changes |
|---|---|---|
| v1.0 | 2026-05-28 |
Initial release. SQL Server engine (SqlServerDeriveEngine), JSON and HTML output formats, {{multi-query:}} expression directive, Execute / RunStoredScript / RunDirectScript endpoints, @parent token support, [AuthorizeTenantAdminAttribute] enforcement, rate limiting via ApiLimit, BizFirst Observe metrics integration.
|
Because the engine name is encoded directly in the
{{multi-query:engineName.CODE}} token, you can introduce an entirely new engine without modifying or invalidating any existing sqlserver templates. Existing templates continue to resolve through SqlServerDeriveEngine unchanged. New templates that target the new engine simply use its EngineName in the directive.