@sfdxy/mule-lint 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -17
- package/dist/package.json +1 -1
- package/dist/src/core/XPathHelper.d.ts.map +1 -1
- package/dist/src/core/XPathHelper.js +8 -0
- package/dist/src/core/XPathHelper.js.map +1 -1
- package/dist/src/engine/LintEngine.d.ts +22 -0
- package/dist/src/engine/LintEngine.d.ts.map +1 -1
- package/dist/src/engine/LintEngine.js +105 -18
- package/dist/src/engine/LintEngine.js.map +1 -1
- package/dist/src/mcp/prompts/index.d.ts +1 -1
- package/dist/src/mcp/prompts/index.d.ts.map +1 -1
- package/dist/src/mcp/prompts/index.js +62 -1
- package/dist/src/mcp/prompts/index.js.map +1 -1
- package/dist/src/mcp/resources/index.js +114 -16
- package/dist/src/mcp/resources/index.js.map +1 -1
- package/dist/src/mcp/tools/getRuleDetails.d.ts.map +1 -1
- package/dist/src/mcp/tools/getRuleDetails.js +30 -1
- package/dist/src/mcp/tools/getRuleDetails.js.map +1 -1
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.d.ts +22 -0
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.d.ts.map +1 -0
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.js +43 -0
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.js.map +1 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.d.ts +24 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.d.ts.map +1 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.js +53 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.js.map +1 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.d.ts +25 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.d.ts.map +1 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.js +59 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.js.map +1 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.d.ts +24 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.d.ts.map +1 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.js +58 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.js.map +1 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.d.ts +23 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.d.ts.map +1 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.js +52 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.js.map +1 -0
- package/dist/src/rules/dataweave/DataWeaveRules.d.ts +11 -4
- package/dist/src/rules/dataweave/DataWeaveRules.d.ts.map +1 -1
- package/dist/src/rules/dataweave/DataWeaveRules.js +20 -20
- package/dist/src/rules/dataweave/DataWeaveRules.js.map +1 -1
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.d.ts +25 -0
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.d.ts.map +1 -0
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.js +63 -0
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.js.map +1 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.d.ts +24 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.d.ts.map +1 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.js +65 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.js.map +1 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.d.ts +28 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.d.ts.map +1 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.js +70 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.js.map +1 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.d.ts +23 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.d.ts.map +1 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.js +73 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.js.map +1 -0
- package/dist/src/rules/error-handling/GenericErrorRule.d.ts +15 -3
- package/dist/src/rules/error-handling/GenericErrorRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/GenericErrorRule.js +58 -18
- package/dist/src/rules/error-handling/GenericErrorRule.js.map +1 -1
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts +14 -15
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js +59 -38
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js.map +1 -1
- package/dist/src/rules/error-handling/TryScopeRule.d.ts +5 -0
- package/dist/src/rules/error-handling/TryScopeRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/TryScopeRule.js +30 -7
- package/dist/src/rules/error-handling/TryScopeRule.js.map +1 -1
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.d.ts +27 -0
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.d.ts.map +1 -0
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.js +46 -0
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.js.map +1 -0
- package/dist/src/rules/index.d.ts +1 -1
- package/dist/src/rules/index.d.ts.map +1 -1
- package/dist/src/rules/index.js +50 -8
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/logging/LoggerPayloadRule.d.ts +15 -0
- package/dist/src/rules/logging/LoggerPayloadRule.d.ts.map +1 -1
- package/dist/src/rules/logging/LoggerPayloadRule.js +48 -4
- package/dist/src/rules/logging/LoggerPayloadRule.js.map +1 -1
- package/dist/src/rules/operations/FlowRefTargetExistsRule.d.ts +23 -0
- package/dist/src/rules/operations/FlowRefTargetExistsRule.d.ts.map +1 -0
- package/dist/src/rules/operations/FlowRefTargetExistsRule.js +58 -0
- package/dist/src/rules/operations/FlowRefTargetExistsRule.js.map +1 -0
- package/dist/src/rules/operations/UnusedFlowRule.d.ts +20 -0
- package/dist/src/rules/operations/UnusedFlowRule.d.ts.map +1 -1
- package/dist/src/rules/operations/UnusedFlowRule.js +73 -7
- package/dist/src/rules/operations/UnusedFlowRule.js.map +1 -1
- package/dist/src/rules/operations/UnusedVariableRule.d.ts +31 -0
- package/dist/src/rules/operations/UnusedVariableRule.d.ts.map +1 -0
- package/dist/src/rules/operations/UnusedVariableRule.js +103 -0
- package/dist/src/rules/operations/UnusedVariableRule.js.map +1 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.d.ts +28 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.d.ts.map +1 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.js +56 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.js.map +1 -0
- package/dist/src/rules/performance/ReconnectionStrategyRule.d.ts +7 -4
- package/dist/src/rules/performance/ReconnectionStrategyRule.d.ts.map +1 -1
- package/dist/src/rules/performance/ReconnectionStrategyRule.js +44 -24
- package/dist/src/rules/performance/ReconnectionStrategyRule.js.map +1 -1
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.d.ts +36 -0
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.d.ts.map +1 -0
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.js +124 -0
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.js.map +1 -0
- package/dist/src/rules/security/HardcodedCredentialsRule.d.ts +4 -0
- package/dist/src/rules/security/HardcodedCredentialsRule.d.ts.map +1 -1
- package/dist/src/rules/security/HardcodedCredentialsRule.js +15 -0
- package/dist/src/rules/security/HardcodedCredentialsRule.js.map +1 -1
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.d.ts +25 -0
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.d.ts.map +1 -0
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.js +59 -0
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.js.map +1 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.d.ts +23 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.d.ts.map +1 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.js +45 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.js.map +1 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.d.ts +25 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.d.ts.map +1 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.js +63 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.js.map +1 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.d.ts +26 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.d.ts.map +1 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.js +61 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.js.map +1 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.d.ts +34 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.d.ts.map +1 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.js +76 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.js.map +1 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.d.ts +25 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.d.ts.map +1 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.js +111 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.js.map +1 -0
- package/dist/src/rules/yaml/YamlRules.d.ts +6 -2
- package/dist/src/rules/yaml/YamlRules.d.ts.map +1 -1
- package/dist/src/rules/yaml/YamlRules.js +15 -11
- package/dist/src/rules/yaml/YamlRules.js.map +1 -1
- package/dist/src/types/Rule.d.ts +13 -0
- package/dist/src/types/Rule.d.ts.map +1 -1
- package/docs/README.md +87 -27
- package/docs/best-practices/ci-cd.md +135 -0
- package/docs/best-practices/connector-patterns.md +253 -0
- package/docs/best-practices/dataweave-patterns.md +370 -0
- package/docs/best-practices/deployment-2026.md +171 -0
- package/docs/best-practices/error-handling.md +277 -0
- package/docs/best-practices/event-driven-patterns.md +424 -0
- package/docs/best-practices/logging.md +163 -0
- package/docs/best-practices/mulesoft-best-practices.md +72 -865
- package/docs/best-practices/performance.md +273 -0
- package/docs/best-practices/rules-catalog.md +337 -29
- package/docs/best-practices/security.md +181 -0
- package/docs/best-practices/testing.md +190 -0
- package/docs/best-practices/variable-contracts.md +191 -0
- package/docs/linter/architecture.md +119 -64
- package/package.json +1 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Performance Best Practices
|
|
2
|
+
|
|
3
|
+
> **Applies to:** All
|
|
4
|
+
> **Related Rules:** `MULE-501` · `MULE-502` · `MULE-503` · `PERF-002` · `RES-001` · `RES-002` · `MULE-801`
|
|
5
|
+
> **Last Updated:** April 2026
|
|
6
|
+
|
|
7
|
+
## When to Read This
|
|
8
|
+
|
|
9
|
+
Read this when optimizing flow performance, configuring connection pooling, implementing async patterns, or reviewing a project for production readiness.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Key Principles
|
|
14
|
+
|
|
15
|
+
1. **Set explicit timeouts** — avoid hanging connections and resource leaks
|
|
16
|
+
2. **Handle async errors** — async scopes silently swallow errors
|
|
17
|
+
3. **Limit choice complexity** — refactor large choice blocks to DWL lookups
|
|
18
|
+
4. **Configure connection pools** — essential for production throughput
|
|
19
|
+
5. **Use streaming for large payloads** — avoid loading everything into memory
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Patterns
|
|
24
|
+
|
|
25
|
+
### Pattern 1: Async Scope Error Handling
|
|
26
|
+
|
|
27
|
+
Async scopes don't propagate errors to the parent flow — they're silently lost:
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- ❌ Bad — errors silently swallowed -->
|
|
31
|
+
<async>
|
|
32
|
+
<http:request config-ref="HTTP_Config" path="/webhook"/>
|
|
33
|
+
</async>
|
|
34
|
+
|
|
35
|
+
<!-- ✅ Good — explicit error handling inside async -->
|
|
36
|
+
<async>
|
|
37
|
+
<try>
|
|
38
|
+
<http:request config-ref="HTTP_Config" path="/webhook"/>
|
|
39
|
+
<error-handler>
|
|
40
|
+
<on-error-continue>
|
|
41
|
+
<logger category="com.myorg.async" level="ERROR"
|
|
42
|
+
message="#['Async webhook failed: ' ++ error.description]"/>
|
|
43
|
+
</on-error-continue>
|
|
44
|
+
</error-handler>
|
|
45
|
+
</try>
|
|
46
|
+
</async>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Pattern 2: Refactor Large Choice Blocks
|
|
50
|
+
|
|
51
|
+
```xml
|
|
52
|
+
<!-- ❌ Bad — too many when clauses (> 5) -->
|
|
53
|
+
<choice>
|
|
54
|
+
<when expression="#[vars.status == 'NEW']">...</when>
|
|
55
|
+
<when expression="#[vars.status == 'PENDING']">...</when>
|
|
56
|
+
<when expression="#[vars.status == 'APPROVED']">...</when>
|
|
57
|
+
<!-- 10+ more conditions -->
|
|
58
|
+
<otherwise>...</otherwise>
|
|
59
|
+
</choice>
|
|
60
|
+
|
|
61
|
+
<!-- ✅ Good — DataWeave lookup + dynamic flow-ref -->
|
|
62
|
+
<ee:transform>
|
|
63
|
+
<ee:set-variable variableName="handler"><![CDATA[%dw 2.0
|
|
64
|
+
var handlers = {
|
|
65
|
+
"NEW": "new-handler-subflow",
|
|
66
|
+
"PENDING": "pending-handler-subflow",
|
|
67
|
+
"APPROVED": "approved-handler-subflow"
|
|
68
|
+
}
|
|
69
|
+
---
|
|
70
|
+
handlers[vars.status] default "default-handler-subflow"
|
|
71
|
+
]]></ee:set-variable>
|
|
72
|
+
</ee:transform>
|
|
73
|
+
<flow-ref name="#[vars.handler]"/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Pattern 3: HTTP Timeout Configuration
|
|
77
|
+
|
|
78
|
+
```xml
|
|
79
|
+
<!-- ✅ Always set explicit timeouts -->
|
|
80
|
+
<http:request-config name="API_Config"
|
|
81
|
+
responseTimeout="${https.request.responseTimeout}">
|
|
82
|
+
<http:request-connection host="${api.host}" port="${api.port}">
|
|
83
|
+
<http:client-socket-properties>
|
|
84
|
+
<http:tcp-client-socket-properties connectionTimeout="30000"/>
|
|
85
|
+
</http:client-socket-properties>
|
|
86
|
+
</http:request-connection>
|
|
87
|
+
</http:request-config>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> ⚠️ **Grizzly rule:** `connectionIdleTimeout` must be ≥ `responseTimeout`. If idle timeout fires first, it kills the connection before the response arrives.
|
|
91
|
+
|
|
92
|
+
### Pattern 4: Connection Pooling
|
|
93
|
+
|
|
94
|
+
```xml
|
|
95
|
+
<!-- ✅ Good — HTTP with pooling configured -->
|
|
96
|
+
<http:request-config name="API_Config">
|
|
97
|
+
<http:request-connection host="${api.host}" port="${api.port}"
|
|
98
|
+
maxConnections="20"
|
|
99
|
+
connectionIdleTimeout="300000"/>
|
|
100
|
+
</http:request-config>
|
|
101
|
+
|
|
102
|
+
<!-- ✅ Good — Database with pooling -->
|
|
103
|
+
<db:config name="Database_Config">
|
|
104
|
+
<db:my-sql-connection host="${db.host}" port="${db.port}"
|
|
105
|
+
database="${db.name}" user="${db.user}"
|
|
106
|
+
password="${secure::db.password}">
|
|
107
|
+
<db:pooling-profile maxPoolSize="10"
|
|
108
|
+
minPoolSize="2" maxWait="30000"/>
|
|
109
|
+
</db:my-sql-connection>
|
|
110
|
+
</db:config>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Pattern 5: Scatter-Gather Limits
|
|
114
|
+
|
|
115
|
+
Keep parallel routes manageable (< 5–7 routes):
|
|
116
|
+
|
|
117
|
+
```xml
|
|
118
|
+
<scatter-gather>
|
|
119
|
+
<route><flow-ref name="service1-subflow"/></route>
|
|
120
|
+
<route><flow-ref name="service2-subflow"/></route>
|
|
121
|
+
<route><flow-ref name="service3-subflow"/></route>
|
|
122
|
+
</scatter-gather>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Pattern 6: Streaming for Large Payloads
|
|
126
|
+
|
|
127
|
+
Mule 4 uses **file-stored repeatable streams** by default (512 KB in-memory buffer). For large payloads:
|
|
128
|
+
|
|
129
|
+
- **Don't** call `payload` multiple times — it forces stream re-read
|
|
130
|
+
- **Do** store needed values in variables before consuming the stream
|
|
131
|
+
- **Do** use streaming-compatible connectors and DataWeave
|
|
132
|
+
|
|
133
|
+
```xml
|
|
134
|
+
<!-- ✅ Store needed values before processing -->
|
|
135
|
+
<set-variable variableName="recordCount" value="#[sizeOf(payload)]"/>
|
|
136
|
+
<ee:transform>
|
|
137
|
+
<ee:set-payload resource="dwl/transforms/process-batch.dwl"/>
|
|
138
|
+
</ee:transform>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Pattern 7: Pre-batch Bulk Lookup (N+1 Prevention)
|
|
142
|
+
|
|
143
|
+
**Use when:** iterating over 10+ records with `foreach` where each record requires 1+ related record lookups from an external system. Without this pattern, 100 records × 3 lookups = 300 API calls. With it: 3 API calls.
|
|
144
|
+
|
|
145
|
+
**Do NOT use when:** iterating < 5 records (overhead not worth it), lookups are already cached in ObjectStore, or the downstream system doesn't support bulk/IN queries.
|
|
146
|
+
|
|
147
|
+
| Scenario | Bulk Lookup? | Why |
|
|
148
|
+
| ------------------------------------------ | ------------ | ------------------------ |
|
|
149
|
+
| 100 records, each needs 3 related lookups | ✅ | 300 calls → 3 calls |
|
|
150
|
+
| 3 records, each needs 1 lookup | ❌ | Overhead exceeds savings |
|
|
151
|
+
| Lookups already cached in ObjectStore | ❌ | Already optimized |
|
|
152
|
+
| Downstream doesn't support IN/bulk queries | ❌ | Can't batch anyway |
|
|
153
|
+
|
|
154
|
+
**Architecture:**
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
1. Save batch to variable
|
|
158
|
+
2. Extract unique lookup IDs (distinctBy, filter !isEmpty)
|
|
159
|
+
3. Scatter-gather: bulk-fetch all related records in parallel
|
|
160
|
+
4. Build groupBy lookup maps → O(1) resolution per record
|
|
161
|
+
5. Restore original batch as payload
|
|
162
|
+
6. Foreach: resolve from maps (zero API calls)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Implementation:**
|
|
166
|
+
|
|
167
|
+
```xml
|
|
168
|
+
<sub-flow name="bulkResolveLookupsSubFlow">
|
|
169
|
+
<!-- 1. Save the original batch -->
|
|
170
|
+
<set-variable value="#[payload]" variableName="batch"/>
|
|
171
|
+
|
|
172
|
+
<!-- 2. Extract unique lookup IDs -->
|
|
173
|
+
<ee:transform>
|
|
174
|
+
<ee:variables>
|
|
175
|
+
<ee:set-variable variableName="lookupIds"><![CDATA[%dw 2.0
|
|
176
|
+
output application/java
|
|
177
|
+
---
|
|
178
|
+
{
|
|
179
|
+
projectIds: (vars.batch.projectId default [])
|
|
180
|
+
filter (!isEmpty($)) distinctBy $,
|
|
181
|
+
contactIds: (vars.batch.contactId default [])
|
|
182
|
+
filter (!isEmpty($)) distinctBy $
|
|
183
|
+
}]]></ee:set-variable>
|
|
184
|
+
</ee:variables>
|
|
185
|
+
</ee:transform>
|
|
186
|
+
|
|
187
|
+
<!-- 3. Bulk fetch in parallel -->
|
|
188
|
+
<scatter-gather>
|
|
189
|
+
<route>
|
|
190
|
+
<http:request method="GET" path="/Project__c">
|
|
191
|
+
<http:query-params>#[output application/java --- {
|
|
192
|
+
"query": "SELECT Id, Project_ID__c FROM Project__c
|
|
193
|
+
WHERE Project_ID__c IN ('"
|
|
194
|
+
++ (vars.lookupIds.projectIds joinBy "','") ++ "')"
|
|
195
|
+
}]</http:query-params>
|
|
196
|
+
</http:request>
|
|
197
|
+
</route>
|
|
198
|
+
<route>
|
|
199
|
+
<http:request method="GET" path="/Contact">
|
|
200
|
+
<http:query-params>#[output application/java --- {
|
|
201
|
+
"query": "SELECT Id, AccountId FROM Contact
|
|
202
|
+
WHERE Id IN ('"
|
|
203
|
+
++ (vars.lookupIds.contactIds joinBy "','") ++ "')"
|
|
204
|
+
}]</http:query-params>
|
|
205
|
+
</http:request>
|
|
206
|
+
</route>
|
|
207
|
+
</scatter-gather>
|
|
208
|
+
|
|
209
|
+
<!-- 4. Build lookup maps with groupBy -->
|
|
210
|
+
<ee:transform>
|
|
211
|
+
<ee:variables>
|
|
212
|
+
<ee:set-variable variableName="lookupMaps"><![CDATA[%dw 2.0
|
|
213
|
+
output application/java
|
|
214
|
+
---
|
|
215
|
+
{
|
|
216
|
+
projects: (payload.'0'.payload default []) groupBy $.Project_ID__c,
|
|
217
|
+
contacts: (payload.'1'.payload default []) groupBy $.Id
|
|
218
|
+
}]]></ee:set-variable>
|
|
219
|
+
</ee:variables>
|
|
220
|
+
</ee:transform>
|
|
221
|
+
|
|
222
|
+
<!-- 5. Restore original batch (scatter-gather replaced payload) -->
|
|
223
|
+
<set-payload value="#[vars.batch]"/>
|
|
224
|
+
</sub-flow>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Then in the foreach — zero API calls per iteration:**
|
|
228
|
+
|
|
229
|
+
```dataweave
|
|
230
|
+
var resolvedProject = (vars.lookupMaps.projects[payload.projectId] default [])[0]
|
|
231
|
+
---
|
|
232
|
+
{
|
|
233
|
+
message: payload ++ { resolvedProject: resolvedProject },
|
|
234
|
+
recordType: "scheduled-entity"
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Key rules:**
|
|
239
|
+
|
|
240
|
+
- **Always save-and-restore payload** — `scatter-gather` replaces `payload` with its own result object
|
|
241
|
+
- **`distinctBy $`** — deduplicate IDs (same project may appear on 50 records)
|
|
242
|
+
- **`filter !isEmpty($)`** — exclude null/empty IDs before building the IN clause
|
|
243
|
+
- **`groupBy` for O(1) resolution** — returns an array; take `[0]` for the first match
|
|
244
|
+
- **Guard the scatter-gather** — if no IDs exist, skip the bulk fetch entirely (set empty maps)
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Flow Complexity Guidelines
|
|
249
|
+
|
|
250
|
+
| Metric | Threshold | Action |
|
|
251
|
+
| --------------------- | ---------- | ----------------------- |
|
|
252
|
+
| Processors per flow | ≤ 15 | Split into sub-flows |
|
|
253
|
+
| Choice branches | ≤ 5 | Use DWL lookup pattern |
|
|
254
|
+
| Scatter-gather routes | ≤ 7 | Consolidate or sequence |
|
|
255
|
+
| Flow-ref depth | ≤ 5 levels | Flatten chain |
|
|
256
|
+
| Loggers per flow | ≤ 5 | Move to DEBUG level |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Checklist
|
|
261
|
+
|
|
262
|
+
- [ ] `responseTimeout` set on all HTTP request configs
|
|
263
|
+
- [ ] `connectionIdleTimeout >= responseTimeout` (Grizzly rule)
|
|
264
|
+
- [ ] Connection pooling configured for HTTP and Database connectors
|
|
265
|
+
- [ ] Async scopes have internal error handling
|
|
266
|
+
- [ ] Choice blocks have ≤ 5 branches (refactor large ones)
|
|
267
|
+
- [ ] Scatter-gather has ≤ 7 routes
|
|
268
|
+
- [ ] Reconnection strategies on all outbound connectors
|
|
269
|
+
- [ ] Pre-batch bulk lookup used for foreach with 10+ records needing related lookups (Pattern 7)
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
**See also:** [Connector Patterns](connector-patterns.md) · [Error Handling](error-handling.md)
|