Skip to content

Commit 22544a6

Browse files
Merge branch 'main' into patch-2
2 parents 4b41fc5 + d87317c commit 22544a6

49 files changed

Lines changed: 1313 additions & 545 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

articles/api-management/TOC.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,16 @@
293293
- name: Reuse policy configurations
294294
displayName: policy fragments
295295
href: policy-fragments.md
296+
- name: Build advanced execution pipeline
297+
items:
298+
- name: Architecture using policy fragments
299+
href: fragment-pipeline-architecture.md
300+
- name: Variable management in execution pipeline
301+
href: fragment-variable-management.md
302+
- name: Central metadata cache for policy fragments
303+
href: fragment-metadata-cache.md
304+
- name: Policy fragment injection and coordination
305+
href: fragment-policy-coordination.md
296306
- name: Error handling
297307
href: api-management-error-handling-policies.md
298308
- name: Advanced monitoring
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
---
2+
title: Central Metadata Cache for Policy Fragments
3+
titleSuffix: Azure API Management
4+
description: Implementation guidance for shared metadata caching pattern across policy fragments in Azure API Management.
5+
services: api-management
6+
author: nicolela
7+
8+
ms.service: azure-api-management
9+
ms.topic: concept-article
10+
ms.date: 02/10/2026
11+
ms.author: nicolela
12+
---
13+
14+
# Central metadata cache for policy fragments
15+
16+
[!INCLUDE [api-management-availability-all-tiers](../../includes/api-management-availability-all-tiers.md)]
17+
18+
When multiple policy fragments need access to shared metadata such as common configuration data, use a cross-request caching approach to optimize performance. Rather than parsing metadata repeatedly in each fragment, a parse-once, cache-everywhere approach dramatically improves performance while ensuring data consistency. With this approach, metadata is **parsed once** on the first request when the cache is empty, then **retrieved from the cache** for all subsequent requests until the cache expires or the cache version changes.
19+
20+
## Recommended approach
21+
22+
This approach requires two fragments: one for storing shared metadata and another for parsing and caching the metadata.
23+
24+
### 1. Metadata fragment
25+
26+
The metadata fragment serves as the single source of truth for shared metadata accessed by other fragments in the pipeline:
27+
- **Centralized JSON storage**: Stores all metadata as JSON.
28+
- **Cache settings**: Includes cache settings with versioning and duration (Time to Live, or TTL).
29+
30+
### 2. Parsing and caching fragment
31+
32+
The parsing and caching fragment implements the following behaviors:
33+
34+
- **Single parse operation**: Uses `JObject.Parse()` to parse the JSON stored in the metadata fragment once at the start of each pipeline request if the cache is empty.
35+
- **Cross-request caching**: Stores and retrieves parsed metadata sections as a `JObject` using the built-in [cache-store-value](cache-store-value-policy.md) and [cache-lookup-value](cache-lookup-value-policy.md) policies across multiple requests.
36+
- **Cache-first access**: Subsequent requests retrieve a parsed `JObject` directly from the cache, providing immediate access to all fragments without reparsing.
37+
- **Cache invalidation**: Cache refreshes when the metadata version changes or the cache duration (TTL) expires.
38+
39+
## Implementation details
40+
41+
To implement this pattern, insert both fragments into a product or API policy definition at the beginning of the inbound phase. The metadata fragment must be inserted first, followed by the parsing and caching fragment. For example:
42+
43+
```xml
44+
<policies>
45+
<inbound>
46+
<base />
47+
<include-fragment fragment-id="metadata-fragment" />
48+
<include-fragment fragment-id="parse-cache-fragment" />
49+
</inbound>
50+
</policies>
51+
```
52+
53+
### Metadata fragment example
54+
55+
The `metadata-fragment.xml` fragment stores shared JSON metadata in a [context variables](api-management-policy-expressions.md#ContextVariables) named `metadata-config`:
56+
57+
```xml
58+
<!-- Single source of truth for all shared metadata -->
59+
<fragment fragment-id="metadata-fragment">
60+
<set-variable name="metadata-config" value="@{return @"{
61+
'cache-settings': {
62+
'config-version': '1.0',
63+
'ttl-seconds': 3600,
64+
'feature-flags': {
65+
'enable-cross-request-cache': true,
66+
'cache-bypass-header': 'X-Config-Cache-Bypass'
67+
}
68+
},
69+
'logging': {
70+
'level': 'INFO',
71+
'enabled': true
72+
},
73+
'rate-limits': {
74+
'premium': { 'requests-per-minute': 1000 },
75+
'standard': { 'requests-per-minute': 100 },
76+
'basic': { 'requests-per-minute': 20 }
77+
}
78+
}";}" />
79+
</fragment>
80+
```
81+
82+
### Parsing and caching fragment example
83+
84+
The `parse-cache-fragment.xml` fragment parses the JSON stored in the `metadata-config` context variable once and provides access to the resulting `JObject`. The `metadata-config` variable must already be set by `metadata-fragment.xml`:
85+
86+
```xml
87+
<fragment fragment-id="parse-cache-fragment">
88+
<!-- Extract cache settings from metadata-config to determine cache version and TTL -->
89+
<set-variable name="cache-config-temp" value="@{
90+
try {
91+
var configStr = context.Variables.GetValueOrDefault<string>("metadata-config", "{}");
92+
if (string.IsNullOrEmpty(configStr) || configStr == "{}") {
93+
return "{\"version\":\"1.0\",\"enabled\":true,\"ttl\":3600}";
94+
}
95+
96+
var tempConfig = JObject.Parse(configStr);
97+
var cacheSettings = tempConfig["cache-settings"] as JObject;
98+
99+
var result = new JObject();
100+
result["version"] = cacheSettings?["config-version"]?.ToString() ?? "1.0";
101+
result["enabled"] = cacheSettings?["feature-flags"]?["enable-cross-request-cache"]?.Value<bool>() ?? true;
102+
result["ttl"] = cacheSettings?["ttl-seconds"]?.Value<int>() ?? 3600;
103+
104+
return result.ToString(Newtonsoft.Json.Formatting.None);
105+
} catch {
106+
return "{\"version\":\"1.0\",\"enabled\":true,\"ttl\":3600}";
107+
}
108+
}" />
109+
110+
<!-- Parse cache configuration -->
111+
<set-variable name="cache-settings-parsed" value="@{
112+
return JObject.Parse(context.Variables.GetValueOrDefault<string>("cache-config-temp", "{}"));
113+
}" />
114+
115+
<!-- Build cache key with version from cache settings -->
116+
<set-variable name="cache-key" value="@{
117+
var settings = context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed");
118+
var version = settings?["version"]?.ToString() ?? "1.0";
119+
return "metadata-config-parsed-v" + version;
120+
}" />
121+
122+
<!-- Try to get from APIM cache -->
123+
<cache-lookup-value key="@(context.Variables.GetValueOrDefault<string>("cache-key"))" variable-name="cached-config" />
124+
125+
<choose>
126+
<when condition="@(context.Variables.ContainsKey("cached-config"))">
127+
<!-- Cache found - Use cached configuration -->
128+
<set-variable name="config-cache-result" value="@(true)" />
129+
130+
<!-- Restore cached config-parsed -->
131+
<set-variable name="config-parsed" value="@(context.Variables.GetValueOrDefault<JObject>("cached-config"))" />
132+
133+
<!-- Extract sections from cached metadata JObject -->
134+
<set-variable name="config-logging" value="@{
135+
var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
136+
return config["logging"] as JObject ?? new JObject();
137+
}" />
138+
139+
<set-variable name="config-rate-limits" value="@{
140+
var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
141+
return config["rate-limits"] as JObject ?? new JObject();
142+
}" />
143+
</when>
144+
<otherwise>
145+
<!-- Cache miss - Parse and store in cache -->
146+
<set-variable name="config-cache-result" value="@(false)" />
147+
148+
<!-- Parse metadata-config JSON -->
149+
<set-variable name="config-parsed" value="@{
150+
var configStr = context.Variables.GetValueOrDefault<string>("metadata-config", "{}");
151+
return JObject.Parse(configStr);
152+
}" />
153+
154+
<!-- Extract commonly used sections for direct access -->
155+
<set-variable name="config-logging" value="@{
156+
var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
157+
return config["logging"] as JObject ?? new JObject();
158+
}" />
159+
160+
<set-variable name="config-rate-limits" value="@{
161+
var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
162+
return config["rate-limits"] as JObject ?? new JObject();
163+
}" />
164+
165+
<!-- Store parsed metadata JObject in cache -->
166+
<cache-store-value key="@(context.Variables.GetValueOrDefault<string>("cache-key"))"
167+
value="@(context.Variables.GetValueOrDefault<JObject>("config-parsed"))"
168+
duration="@(context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed")?["ttl"]?.Value<int>() ?? 3600)" />
169+
</otherwise>
170+
</choose>
171+
</fragment>
172+
```
173+
174+
### Using metadata in other fragments
175+
176+
Other fragments can now access parsed metadata sections directly. For example:
177+
178+
```xml
179+
<fragment fragment-id="request-logging-fragment">
180+
<!-- Access logging metadata JObject without reparsing -->
181+
<set-variable name="config-logging" value="@{
182+
return context.Variables.GetValueOrDefault<JObject>("config-logging", new JObject());
183+
}" />
184+
</fragment>
185+
```
186+
187+
### Cache settings and invalidation
188+
189+
The `parse-cache-fragment.xml` fragment uses the cache settings stored in the `metadata-fragment.xml` fragment to determine caching behavior and invalidation. For example, the settings can be changed as follows:
190+
191+
```xml
192+
<!-- Example: Updated cache settings in the metadata fragment -->
193+
'cache-settings': {
194+
'config-version': '1.0.1', <!-- Change version to invalidate cache -->
195+
'ttl-seconds': 7200, <!-- Increase TTL to 2 hours -->
196+
'feature-flags': {
197+
'enable-cross-request-cache': true,
198+
'cache-bypass-header': 'X-Config-Cache-Bypass'
199+
}
200+
}
201+
```
202+
203+
**How cache invalidation works:** The `parse-cache-fragment.xml` fragment constructs cache keys using the `config-version` value (for example, `metadata-config-v1.0.1`). When the version is changed to `1.0.2`, a new cache key is created (`metadata-config-v1.0.2`). Since no cached data exists for the new key, the fragment parses fresh metadata JSON.
204+
205+
**To force cache refresh:** Update the `config-version` in the `metadata-fragment.xml` fragment. Because the cache settings are parsed on every request before the cache lookup occurs, changes to the cache configuration take effect immediately.
206+
207+
## Testing and debugging
208+
209+
### Cache result tracking
210+
211+
The `parse-cache-fragment.xml` fragment sets a `config-cache-result` variable. This variable is useful for logging and in response headers for debugging:
212+
213+
```xml
214+
<!-- Add cache status to response headers for debugging -->
215+
<set-header name="X-Config-Cache-Result" exists-action="override">
216+
<value>@(context.Variables.GetValueOrDefault<bool>("config-cache-result", false).ToString())</value>
217+
</set-header>
218+
```
219+
220+
### Cache bypass
221+
222+
To disable caching, use the cache bypass header:
223+
224+
```bash
225+
curl -H "X-Config-Cache-Bypass: true" https://your-gateway.com/api
226+
```
227+
228+
The `parse-cache-fragment.xml` fragment checks for the bypass header after parsing cache settings:
229+
230+
```xml
231+
<!-- Check if cache bypass is requested -->
232+
<set-variable name="cache-bypass-requested" value="@{
233+
var settings = context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed");
234+
var bypassHeader = settings?["bypass-header"]?.ToString() ?? "X-Config-Cache-Bypass";
235+
return context.Request.Headers.GetValueOrDefault(bypassHeader, "").ToLower() == "true";
236+
}" />
237+
```
238+
239+
The bypass check is then used in the caching decision logic:
240+
241+
```xml
242+
<when condition="@{
243+
var settings = context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed");
244+
var enabled = settings?["enabled"]?.Value<bool>() ?? false;
245+
var bypass = context.Variables.GetValueOrDefault<bool>("cache-bypass-requested", false);
246+
return enabled && !bypass;
247+
}">
248+
<!-- Cross-request caching is enabled and not bypassed -->
249+
</when>
250+
```
251+
252+
## Best practices
253+
254+
### Handle JSON parsing failures with error logging and defaults
255+
256+
Implement error handling for JSON parsing operations to prevent fragment failures and provide fallback behavior. Wrap `JObject.Parse()` operations in try-catch blocks with meaningful default values:
257+
258+
```xml
259+
<set-variable name="config-parsed" value="@{
260+
try {
261+
var configJson = context.Variables.GetValueOrDefault<string>("metadata-config", "{}");
262+
return JObject.Parse(configJson);
263+
} catch (Exception ex) {
264+
// Return default configuration on parse failure
265+
return JObject.Parse(@"{
266+
'logging': { 'level': 'ERROR', 'enabled': false },
267+
'rate-limits': { 'default': { 'requests-per-minute': 10 } }
268+
}");
269+
}
270+
}" />
271+
272+
<!-- Log parse error using trace policy -->
273+
<choose>
274+
<when condition="@(context.Variables.ContainsKey("parse-error"))">
275+
<trace source="config-parse" severity="error">
276+
<message>@("JSON parse failed: " + context.Variables.GetValueOrDefault<string>("parse-error"))</message>
277+
</trace>
278+
</when>
279+
</choose>
280+
```
281+
282+
## Related content
283+
284+
- **[Architecture for building advanced execution pipelines with policy fragments](fragment-pipeline-architecture.md)** - Foundational patterns for designing modular, scalable policy fragment architectures with clear separation of concerns.
285+
- **[Variable management for policy fragments](fragment-variable-management.md)** - Comprehensive guidance on context variable handling, safe access patterns, and inter-fragment communication.
286+
- **[Policy injection and coordination with fragments](fragment-policy-coordination.md)** - Fragment injection patterns and coordination between product and API policies.
287+
- **[Custom caching in Azure API Management](api-management-sample-cache-by-key.md)** - Learn how to cache items by key and modify the key using request headers.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
title: Architecture for Building Advanced Execution Pipelines with Policy Fragments
3+
titleSuffix: Azure API Management
4+
description: Foundational patterns for designing modular, scalable policy fragment architectures in Azure API Management with clear separation of concerns.
5+
services: api-management
6+
author: nicolela
7+
8+
ms.service: azure-api-management
9+
ms.topic: concept-article
10+
ms.date: 02/10/2026
11+
ms.author: nicolela
12+
---
13+
14+
# Architecture for building advanced execution pipelines with policy fragments
15+
16+
[!INCLUDE [api-management-availability-all-tiers](../../includes/api-management-availability-all-tiers.md)]
17+
18+
To build advanced pipeline scenarios with custom behavior executed throughout the request and response lifecycle, you can use [policy fragments](policy-fragments.md). Policy fragments are centrally managed, reusable XML snippets that can contain [policy](api-management-howto-policies.md) configurations and [policy expressions](api-management-policy-expressions.md). Policy fragments offer the following capabilities:
19+
20+
- **Phases**: Custom behavior can be distributed across inbound, backend, outbound, and error phases using fragments.
21+
- **Sequential processing**: Fragments execute in a defined order with clear dependencies, enabling business logic to unfold step by step. Each fragment completes before the next starts, with no parallel execution.
22+
- **Scalable architecture**: Fragments provide efficient execution and caching to support enterprise-scale scenarios.
23+
- **Maintainable design**: Clear separation of concerns between fragments makes the system easier to understand and maintain.
24+
25+
## Recommended patterns
26+
27+
Follow these four recommended patterns to build advanced pipelines using policy fragments:
28+
29+
### 1. Modularity
30+
31+
Design each fragment with a single, well-defined responsibility. Each fragment should focus on one specific concern such as authentication, logging, or rate limiting. For example, a fragment named `security-authentication` should only contain authentication behavior and logic.
32+
33+
### 2. Data sharing
34+
35+
Share data between fragments using [context variables](api-management-policy-expressions.md#ContextVariables).
36+
37+
- **Define data contracts**: Use context variables as data contracts for sequentially passing data between fragments. For example, a `security-authentication` fragment can set a context variable called `user-id` that contains the authenticated user's extracted ID, making it available for downstream fragments in the pipeline. Under the covers, each request maintains its own isolated `context.Variables` dictionary for storing context variables, ensuring thread-safe communication between fragments. See [Variable Management for Policy Fragments](fragment-variable-management.md) for details.
38+
39+
- **Cache shared data**: When multiple fragments need to access the same data, use cross-request caching to reduce parsing overhead and improve performance. See [Central Metadata Cache for Policy Fragments](fragment-metadata-cache.md) for implementation guidance.
40+
41+
### 3. Fragment execution behavior
42+
43+
Define execution behavior by inserting fragments sequentially in [product and API policy](api-management-howto-policies.md#scopes) definitions.
44+
45+
- **Sequential execution**: Insert fragments in the order they need to execute, to enable workflows where later fragments depend on variables set by earlier fragments.
46+
47+
- **Policy division**: Divide fragments across product and API policies according to scope of custom logic.
48+
49+
- **Shared fragments**: To avoid duplication and maintain consistent behavior, you can reuse the same fragment in both product and API policy levels.
50+
51+
For detailed guidance on coordinating fragment execution across policy scopes, see [Policy Injection and Coordination with Fragments](fragment-policy-coordination.md).
52+
53+
### 4. Performance optimization
54+
55+
Optimize fragments for maximum performance.
56+
57+
- **Keep fragments under 32-KB**: Stay within the [32-KB limit](policy-fragments.md) to ensure fast in-memory parsing and avoid slower file-based processing. The limit includes all whitespace, comments, and XML markup - not just the code logic. To stay under the limit:
58+
- Extract lengthy values, such as multi-line JSON, to [Named Values](api-management-howto-properties.md).
59+
- Use shorter variable names.
60+
- Split complex fragments into smaller ones.
61+
62+
- **Apply recommended patterns**: Follow the recommendations outlined earlier in this article. Most importantly:
63+
- Eliminate redundant parsing by implementing a metadata cache.
64+
- Use early exit patterns for health checks, authentication failures, variable existence, and other scenarios where definitive responses can be determined without further processing.
65+
- Follow processing optimizations such as minimizing variable access.
66+
67+
For comprehensive implementation details, see the guidance in the below [Related content](#related-content) section.
68+
69+
## Related content
70+
71+
- **[Variable management for policy fragments](fragment-variable-management.md)** - Comprehensive guidance on context variable handling, safe access patterns, and inter-fragment communication.
72+
- **[Central metadata cache for policy fragments](fragment-metadata-cache.md)** - Implementation guidance for shared metadata caching patterns across fragments.
73+
- **[Policy injection and coordination with fragments](fragment-policy-coordination.md)** - Fragment injection patterns and coordination between product and API policies.

0 commit comments

Comments
 (0)