Cache-Aside Task (Type: 18)
The Cache-Aside Task implements the cache-aside (read-through) pattern as a single task. Designers no longer hand-wire "check cache → call service → write cache" as three separate tasks; the engine centralizes that flow along with TTL, consistency and cache-failure semantics.
Behavior:
- If the resolved key is present in the cache (hit) → the cached value is returned; the
sourceTaskis not executed. - On a miss (or
forceRefresh: true) → thesourceTaskruns, its raw result is written to the cache withttlInSeconds+consistency, and returned. AsourceMapping, if present, shapes the result on read.
Task Definition
Schema:
task-definition.schema.json
{
"key": "cache-customer-profile",
"version": "1.0.0",
"domain": "core",
"flow": "sys-tasks",
"flowVersion": "1.0.0",
"tags": ["cache", "customer"],
"attributes": {
"type": "18",
"config": {
"key": "customer:42:profile",
"storeName": "vnext-state",
"ttlInSeconds": 300,
"consistency": "Eventual",
"sourceTask": { "key": "get-customer-http", "domain": "core", "flow": "sys-tasks", "version": "1.0.0" },
"sourceMapping": { "location": "./src/mappings/get-customer-cached.csx", "code": "<base64>" },
"bypassOnCacheError": true,
"forceRefresh": false
}
}
}
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes* | Cache key, used verbatim. For a dynamic key, the transition mapping's InputHandler calls task.SetCacheKey(...) (the same standard mechanism as the State Store task). *If no static key is given, the key must be set by the mapping |
storeName | string | No | Dapr state store component used as the cache. When omitted, the executing runtime's DAPR_STATE_STORE_NAME configuration value is used |
ttlInSeconds | integer | No | Time-to-live for the cached entry (Dapr ttlInSeconds metadata). When absent or 0, the entry has no expiry |
consistency | string | No | Eventual (default) or Strong — passed to the state store on read and write |
sourceTask | object | Yes | Reference (key / domain / flow / version) to the task executed on a cache miss. Must be a remotely-invokable type (HTTP / SOAP / Dapr / GetInstanceData). flow defaults to the runtime tasks schema when omitted |
sourceMapping | object | No | .csx mapping (location + base64 code) applied to the cached (raw) result before it is returned. Runs as the mapping's OutputHandler on read — on both hits and misses |
bypassOnCacheError | boolean | No | true (default): cache read/write failures fall back to the source task instead of failing the pipeline. false: cache errors surface as a task failure (error boundary applies) |
forceRefresh | boolean | No | true: skip the cache read; always execute the source task and overwrite the entry |
Read-Through Flow
| State | Behavior |
|---|---|
| Cache HIT | Resolved key found → the cached value is returned; sourceTask is not executed |
| Cache MISS | sourceTask runs → its raw result is written to the cache with ttlInSeconds + consistency → returned |
forceRefresh: true | Behaves as a miss regardless of cache content; refreshes the entry |
Architecture
Follows the exact same split as the State Store task:
CacheAsideTaskExecutor(Orchestration / Application) runs the input mapping, resolves thesourceTaskinto an envelope, then sends acacheasideTaskEnvelopeto the Execution service viaIRemoteInvokerService, and applies the output mapping (sourceMapping) on the way out.CacheAsideTaskInvoker(Execution) performs the state store access throughDaprClient(samecustom:prefix / TTL / consistency asStateStoreTaskInvoker, via the sharedIStateStoreClient). On a miss it runs the pre-resolvedsourceTaskenvelope through the local invoker registry (e.g. an HTTP source runs on the same Execution service's HTTP invoker) and writes the raw result to the cache.
Because the scripting engine exists only in the Orchestration runtime, the cache stores the raw source result; shaping (sourceMapping) is applied on every read (hit and miss) in the executor's output stage. The task result participates in instance-data versioning (Patch bump) like any other task result.
Key Naming Convention (custom: prefix)
Cache keys share the same custom: prefix as the State Store task, so a CacheAsideTask and a StateStoreTask targeting the same logical key hit the same physical entry — a designer can pre-warm or invalidate a cache-aside entry with a plain State Store set/delete task.
- Task config
key: "customer:42:profile"→ store keycustom:customer:42:profile
Semantics
- Cache infrastructure error +
bypassOnCacheError: true: a warning is logged, thesourceTaskruns, and its result is returned (best-effort cache write; a failed write is ignored). - Cache infrastructure error +
bypassOnCacheError: false: the task fails and flows into the error boundary chain. - Source task business failure: propagated as this task's failure; nothing is cached.
sourceTaskmust be a remotely-invokable type (HTTP / SOAP / Dapr / GetInstanceData). Since it runs on the Execution service on a miss, a local-only Script / Condition cannot be a source.- When
storeNameis omitted, the store is resolved from the Execution runtime'sDAPR_STATE_STORE_NAMEvalue (vnext-statein the shipped environments).
Dynamic Key
Instead of a static key, the key can be computed per request/context. The same standard mechanism as the State Store task is used: the transition mapping's InputHandler mutates the task to set the key (there is no bespoke templating engine):
public async Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
{
var customerId = context.Headers["customerId"];
((CacheAsideTask)task).SetCacheKey($"customer:{customerId}:profile");
return new ScriptResponse();
}
To cache an entire function's output (rather than a single task), see the cache block on the function definition — there the key can be computed with a Dynamic Expresso keyExpression and invalidated via a generation namespace.
Examples
Simple read-through (HTTP source)
"attributes": {
"type": "18",
"config": {
"key": "customer:42:profile",
"storeName": "vnext-state",
"ttlInSeconds": 300,
"sourceTask": { "key": "get-customer-http", "domain": "core", "flow": "sys-tasks", "version": "1.0.0" }
}
}
forceRefresh — always refresh the cache
"attributes": {
"type": "18",
"config": {
"key": "customer:42:profile",
"sourceTask": { "key": "get-customer-http", "domain": "core", "flow": "sys-tasks", "version": "1.0.0" },
"forceRefresh": true
}
}
sourceMapping — shape the raw result before caching/returning
"attributes": {
"type": "18",
"config": {
"key": "customer:42:profile",
"sourceTask": { "key": "get-customer-http", "domain": "core", "flow": "sys-tasks", "version": "1.0.0" },
"sourceMapping": { "location": "./src/mappings/get-customer-cached.csx", "code": "<base64>" },
"ttlInSeconds": 300
}
}
Standard Response
{
"Data": { "name": "Ada" },
"StatusCode": 200,
"IsSuccess": true,
"ErrorMessage": null,
"Metadata": {
"CacheHit": true,
"Refreshed": false,
"Key": "custom:customer:42:profile",
"ETag": "3"
},
"TaskType": "CacheAside"
}
Metadata.CacheHit = true→ the value came from the cache (the source did not run).Metadata.CacheHit = falseandMetadata.Refreshed = true→ miss/forceRefresh; the source ran and the result was cached.Metadata.Keyreports the prefixed store key.
Related
- Tasks Overview — task types and the reference mechanism
- State Store Task — the shared caching primitive (get/set/delete); same
custom:prefix and state store - Runtime doc: cache-aside-task.md (vnext)
- Schema source: task-definition.schema.json (vnext-schema)