@sfdxy/mule-lint 1.21.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.
@@ -0,0 +1,370 @@
1
+ # DataWeave Best Practices
2
+
3
+ > **Applies to:** All
4
+ > **Related Rules:** `DW-001` · `DW-002` · `DW-003` · `DW-004` · `DW-005`
5
+ > **Last Updated:** April 2026
6
+
7
+ ## When to Read This
8
+
9
+ Read this when writing DataWeave transformations, creating reusable DWL modules, or debugging type coercion issues with connectors.
10
+
11
+ ---
12
+
13
+ ## Key Principles
14
+
15
+ 1. **Externalize complex transforms** — inline DWL > 5 lines should be in a `.dwl` file
16
+ 2. **Create reusable modules** — shared logic goes in `dwl/modules/`
17
+ 3. **Use full import paths** — `import X from dwl::modules::Y` (short paths fail at runtime)
18
+ 4. **Match connector type expectations** — SF connector needs Java types, not strings
19
+ 5. **Use kebab-case for file names** — `transform-order.dwl`, not `transformOrder.dwl`
20
+
21
+ ---
22
+
23
+ ## Project Structure
24
+
25
+ ```
26
+ src/main/resources/dwl/
27
+ ├── modules/ # Reusable DWL modules
28
+ │ ├── salesforceUtility.dwl # Type coercion for SF connector
29
+ │ ├── oauth1Header.dwl # OAuth 1.0 HMAC-SHA256 header generator
30
+ │ └── dateUtils.dwl # Shared date formatting functions
31
+ ├── transforms/ # Entity-specific transforms
32
+ │ ├── sf-account-to-ns-customer.dwl
33
+ │ ├── salesforce-upsert-response.dwl
34
+ │ ├── error-payload.dwl
35
+ │ └── ...
36
+ ├── lookups/ # Enum mapping modules
37
+ │ ├── countryMap.dwl # ISO country → target system enum
38
+ │ └── currencyMap.dwl # ISO currency → target system ID
39
+ └── dictionaries/ # Environment-specific lookup data
40
+ ├── customer-segment-dictionary-dev.json
41
+ └── customer-segment-dictionary-prod.json
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Patterns
47
+
48
+ ### Pattern 1: External DWL Transform Files
49
+
50
+ **Use when:** any transform exceeds 5 lines of DataWeave.
51
+
52
+ ```xml
53
+ <!-- ❌ Bad — large inline transform -->
54
+ <ee:transform>
55
+ <ee:set-payload><![CDATA[%dw 2.0
56
+ <!-- 50+ lines of DataWeave -->
57
+ ]]></ee:set-payload>
58
+ </ee:transform>
59
+
60
+ <!-- ✅ Good — external file reference -->
61
+ <ee:transform doc:name="SF Account → NS Customer">
62
+ <ee:message>
63
+ <ee:set-payload resource="dwl/transforms/sf-account-to-ns-customer.dwl"/>
64
+ </ee:message>
65
+ </ee:transform>
66
+ ```
67
+
68
+ ### Pattern 2: Reusable DWL Modules
69
+
70
+ **Use when:** logic is shared across multiple transforms (date formatting, type coercion, enum lookups).
71
+
72
+ ```dataweave
73
+ // dwl/modules/dateUtils.dwl
74
+ %dw 2.0
75
+
76
+ fun formatDate(date: DateTime) =
77
+ date as String {format: "yyyy-MM-dd"}
78
+
79
+ fun formatDateTime(date: DateTime) =
80
+ date as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"}
81
+
82
+ fun maskPII(value: String) =
83
+ value[0 to 2] ++ "****" ++ value[-2 to -1]
84
+
85
+ fun toErrorResponse(error, correlationId: String) = {
86
+ correlationId: correlationId,
87
+ timestamp: now(),
88
+ error: error.errorType.identifier,
89
+ message: error.description
90
+ }
91
+ ```
92
+
93
+ **Import with full path:**
94
+
95
+ ```dataweave
96
+ %dw 2.0
97
+ import dwl::modules::dateUtils
98
+ output application/json
99
+ ---
100
+ {
101
+ createdDate: dateUtils::formatDate(payload.CreatedDate)
102
+ }
103
+ ```
104
+
105
+ > ⚠️ **Critical:** Always use `dwl::modules::moduleName` as the import path. Short paths like `modules::moduleName` will compile but **fail at runtime**.
106
+
107
+ ### Pattern 3: Type Coercion for Connectors
108
+
109
+ **Use when:** passing data to the Salesforce connector (or any connector that expects Java types).
110
+
111
+ The SF connector rejects ISO datetime strings — they must be Java DateTime objects:
112
+
113
+ ```dataweave
114
+ // dwl/modules/salesforceUtility.dwl
115
+ %dw 2.0
116
+
117
+ var dateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})?$/
118
+ var dateOnlyPattern = /^\d{4}-\d{2}-\d{2}$/
119
+
120
+ fun parseAndConvert(obj: Object) =
121
+ obj mapObject ((value, key) ->
122
+ if ((value is String) and ((value as String) matches dateTimePattern))
123
+ (key): (value as String) as DateTime
124
+ else if ((value is String) and ((value as String) matches dateOnlyPattern))
125
+ (key): (value as String) as Date
126
+ else if ((value is String) and ((value as String) == ""))
127
+ (key): null
128
+ else if (value is Object)
129
+ (key): parseAndConvert(value)
130
+ else
131
+ (key): value
132
+ )
133
+ ```
134
+
135
+ **When to use `output application/java`:**
136
+
137
+ ```dataweave
138
+ %dw 2.0
139
+ import dwl::modules::salesforceUtility
140
+ output application/java // ← Required for SF connector inputs
141
+ ---
142
+ salesforceUtility::parseAndConvert(payload)
143
+ ```
144
+
145
+ Use `output application/java` for transforms that feed directly into connectors (SF, DB, NetSuite SOAP). Use `output application/json` for HTTP response payloads.
146
+
147
+ ### Pattern 4: Cross-System Value Mapping
148
+
149
+ **Use when:** translating enum/picklist values between systems (country codes, currency codes, payment terms, industry classifications). There are 4 strategies — choose based on your data characteristics.
150
+
151
+ #### Strategy A: Rich-Object DWL Lookup (Standardized Reference Data)
152
+
153
+ **Use when:** stable reference data (ISO countries, currencies), ≤ 300 entries, same across all environments, callers need multiple fields (`.name`, `.erp`).
154
+
155
+ ```dataweave
156
+ // dwl/lookups/countryMap.dwl
157
+ %dw 2.0
158
+
159
+ fun countryMap() = {
160
+ "US": { "name": "United States", "erp": "_unitedStates" },
161
+ "CA": { "name": "Canada", "erp": "_canada" },
162
+ "GB": { "name": "United Kingdom", "erp": "_unitedKingdom" }
163
+ // 250+ entries for full ISO 3166...
164
+ }
165
+ ```
166
+
167
+ **Usage — always null-guard before dereferencing:**
168
+
169
+ ```dataweave
170
+ import countryMap from dwl::lookups::countryMap
171
+ ---
172
+ {
173
+ (shippingCountry: countryMap()[payload.ShippingCountry].erp)
174
+ if (payload.ShippingCountry? and (countryMap()[payload.ShippingCountry] != null))
175
+ }
176
+ ```
177
+
178
+ #### Strategy B: Environment-Specific JSON Dictionary
179
+
180
+ **Use when:** target system IDs differ between dev/staging/prod (e.g., ERP sandbox has different internal IDs than production). Flat key → value mapping loaded via `readUrl`.
181
+
182
+ ```json
183
+ // dwl/dictionaries/customer-industry-dictionary-dev.json
184
+ {
185
+ "Education": "1",
186
+ "Financial Services": "13",
187
+ "Government": "14"
188
+ }
189
+ ```
190
+
191
+ ```json
192
+ // dwl/dictionaries/customer-industry-dictionary-prod.json
193
+ {
194
+ "Education": "101",
195
+ "Financial Services": "113",
196
+ "Government": "114"
197
+ }
198
+ ```
199
+
200
+ **Load dynamically using environment property:**
201
+
202
+ ```dataweave
203
+ var industry = readUrl(
204
+ "classpath://dwl/dictionaries/customer-industry-dictionary-$(p('mule.env')).json",
205
+ "application/json"
206
+ )
207
+ ---
208
+ {
209
+ (industryId: industry[payload.Industry]) if (payload.Industry?)
210
+ }
211
+ ```
212
+
213
+ #### Strategy C: Bidirectional DWL Module (Enum Translation)
214
+
215
+ **Use when:** business enums must translate in both directions (CRM → ERP and ERP → CRM), ≤ 50 values, stable values (payment terms, billing frequency).
216
+
217
+ **Option 1 — Computed inverse (recommended for 1:1 mappings):**
218
+
219
+ ```dataweave
220
+ // dwl/lookups/paymentTerms.dwl
221
+ %dw 2.0
222
+
223
+ // Single source of truth
224
+ var crmToErp = {
225
+ "Net 15": "1", "Net 30": "2", "Net 45": "8",
226
+ "Net 60": "3", "Due on receipt": "4"
227
+ }
228
+
229
+ // Auto-computed inverse — no dual maintenance
230
+ var erpToCrm = crmToErp pluck ((value, key) -> { (value): key as String })
231
+ reduce ((item, acc = {}) -> acc ++ item)
232
+
233
+ fun mapPaymentTermToERP(crmValue: String) = crmToErp[crmValue]
234
+ fun mapPaymentTermToCRM(erpId: String) = erpToCrm[erpId]
235
+ ```
236
+
237
+ **Option 2 — Explicit dual dictionary (required for many-to-one):**
238
+
239
+ When 3 CRM statuses map to 1 ERP status, the computed inverse is ambiguous. Maintain both directions explicitly:
240
+
241
+ ```dataweave
242
+ var statusMap = {
243
+ crmToErp: { "Open": "1", "In Progress": "1", "Closed": "2" },
244
+ erpToCrm: { "1": "Open", "2": "Closed" }
245
+ }
246
+ ```
247
+
248
+ #### Strategy D: External API / ObjectStore Cache (Dynamic Catalogs)
249
+
250
+ **Use when:** > 500 values, business users manage mappings at runtime, multiple apps share the same mapping, change frequency > quarterly.
251
+
252
+ ```xml
253
+ <os:object-store name="product-sku-cache"
254
+ entryTtl="3600000"
255
+ expirationInterval="300000"
256
+ maxEntries="500"/>
257
+ ```
258
+
259
+ #### Decision Tree
260
+
261
+ ```
262
+ Does the mapping differ between dev/staging/prod?
263
+ ├── YES → Strategy B (Environment-Specific JSON Dictionary)
264
+ └── NO
265
+ ├── Need lookup in both directions (CRM↔ERP)?
266
+ │ ├── YES, ≤ 50 values → Strategy C (Bidirectional DWL)
267
+ │ └── YES, > 50 values → Strategy D (External)
268
+ └── NO (one direction only)
269
+ ├── Need rich objects (name + ID)? → Strategy A (Rich-Object DWL)
270
+ └── Simple key→value? → Strategy A (flat DWL function)
271
+ ```
272
+
273
+ **Testing guidance:**
274
+
275
+ - **Completeness:** When CRM adds a new picklist value, the lookup silently returns `null`. Add MUnit tests that validate all known enum values have mappings.
276
+ - **Symmetry:** For 1:1 bidirectional mappings, `mapToERP(mapFromERP(x)) == x` should hold.
277
+ - **Environment parity:** For Strategy B, verify that dev and prod dictionary files have the **same keys** (only IDs should differ).
278
+
279
+ > ⚠️ **Always null-guard lookups** — `countryMap()[code]` returns `null` for unknown codes. Dereferencing `.erp` on `null` causes a runtime error.
280
+
281
+ ### Pattern 5: Array Normalization for Connectors
282
+
283
+ **Use when:** connector operations (upsert, create, update) always expect arrays, but input may be a single object.
284
+
285
+ ```dataweave
286
+ %dw 2.0
287
+ import dwl::modules::salesforceUtility
288
+ output application/java
289
+ ---
290
+ var parsed = salesforceUtility::parseAndConvert(payload)
291
+ ---
292
+ if (parsed is Array) parsed else [parsed]
293
+ ```
294
+
295
+ ### Pattern 6: Error Payload Template
296
+
297
+ **Use when:** building standardized error response payloads.
298
+
299
+ ```dataweave
300
+ // dwl/transforms/error-payload.dwl
301
+ %dw 2.0
302
+ output application/json
303
+ ---
304
+ {
305
+ timestamp: now() as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"},
306
+ correlationId: vars.correlationId default "",
307
+ entity: vars.entity default "",
308
+ salesforceId: vars.salesforceId default "",
309
+ errorType: error.errorType.identifier default "UNKNOWN",
310
+ message: error.detailedDescription default error.description default "",
311
+ environment: p('mule.env')
312
+ }
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Java 17 DataWeave Considerations
318
+
319
+ Since Mule 4.9+ mandates Java 17:
320
+
321
+ - `try()` requires explicit import: `import try from dw::Runtime`
322
+ - `error.errorType.identifier` — use `.identifier`, not `.asString` (doesn't exist)
323
+ - Batch Kryo serialization — don't use `output application/java` for HTTP payloads in batch steps
324
+ - `DateTime` coercion is stricter in Java 17 — always test date parsing patterns
325
+
326
+ **Safe error description extraction using `try()`:**
327
+
328
+ ```dataweave
329
+ %dw 2.0
330
+ import try from dw::Runtime
331
+ var errorDesc = try(() -> error.errorMessage.payload.errorDescription default "")
332
+ ---
333
+ {
334
+ "Subject": "Failed to sync record",
335
+ "Error": if (errorDesc.success and !isBlank(errorDesc.result))
336
+ errorDesc.result
337
+ else (error.detailedDescription default "")
338
+ }
339
+ ```
340
+
341
+ > ⚠️ Use `try()` when accessing deeply nested error properties that may not exist. Without it, accessing `error.errorMessage.payload.errorDescription` throws if `payload` is not an object.
342
+
343
+ ---
344
+
345
+ ## File Naming Convention
346
+
347
+ | Convention | Example | Use For |
348
+ | ------------ | ------------------------------ | -------------------------------------------- |
349
+ | `kebab-case` | `transform-order-response.dwl` | Transform files |
350
+ | `camelCase` | `salesforceUtility.dwl` | Module files (matches DWL import convention) |
351
+ | `kebab-case` | `country-map.dwl` | Lookup files |
352
+
353
+ > **Note:** Module filenames use `camelCase` because the DWL import path must match the filename exactly (`import dwl::modules::salesforceUtility`). Renaming to kebab-case would break imports.
354
+
355
+ ---
356
+
357
+ ## Checklist
358
+
359
+ - [ ] No inline DWL exceeding 5 lines — externalize to `.dwl` files
360
+ - [ ] Shared logic in `dwl/modules/` with full import path
361
+ - [ ] `output application/java` used for connector input transforms
362
+ - [ ] `output application/json` used for HTTP response transforms
363
+ - [ ] Type coercion applied before passing data to SF connector
364
+ - [ ] Lookup modules always null-guarded
365
+ - [ ] Array normalization before connector operations
366
+ - [ ] `.dwl` files have Javadoc-style header comments
367
+
368
+ ---
369
+
370
+ **See also:** [Connector Patterns](connector-patterns.md) · [Documentation Standards](documentation-standards.md)
@@ -0,0 +1,171 @@
1
+ # Deployment & Modernization (2026)
2
+
3
+ > **Applies to:** All
4
+ > **Related Rules:** `OPS-001` · `PROJ-001` · `PROJ-002`
5
+ > **Last Updated:** April 2026
6
+
7
+ ## When to Read This
8
+
9
+ Read this when planning deployments, migrating to Java 17, adopting CloudHub 2.0, or modernizing tooling.
10
+
11
+ ---
12
+
13
+ ## Development Tooling (2026)
14
+
15
+ ### Anypoint Code Builder (ACB)
16
+
17
+ ACB (VS Code-based) is the recommended IDE for 2026+, replacing Anypoint Studio for new projects:
18
+
19
+ - **AI-assisted development** via MuleSoft Vibes
20
+ - **AsyncAPI 2.6 support** for event-driven API design
21
+ - **Native API Governance** validation during design
22
+ - **Integrated Exchange** publishing
23
+
24
+ ### MuleSoft CLI Tools
25
+
26
+ ```bash
27
+ # Lint analysis (CI/CD and local)
28
+ npx @sfdxy/mule-lint . -c .mulelintrc.json -f json
29
+
30
+ # Build + test
31
+ mvn clean test -Dmule.env=dev -Dsecure.key=test
32
+
33
+ # Package
34
+ mvn clean package -DskipTests
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Java 17 Migration
40
+
41
+ Mule 4.9+ **mandates Java 17**. Migration checklist:
42
+
43
+ - [ ] Update `pom.xml` compiler settings and runtime version
44
+ - [ ] Run `jdeps` to identify dependencies on removed Java APIs
45
+ - [ ] Audit connector compatibility (most modern connectors are Java 17 compatible)
46
+ - [ ] Recompile custom connectors and Java modules
47
+ - [ ] Test DataWeave patterns — `DateTime` coercion is stricter in Java 17
48
+ - [ ] Update `log4j2.xml` for Java 17 module system changes
49
+ - [ ] Performance test in staging — JIT compiler and GC behavior differ
50
+
51
+ ---
52
+
53
+ ## Deployment Models
54
+
55
+ ### CloudHub 2.0 (Kubernetes-based)
56
+
57
+ | Feature | CloudHub 1.0 | CloudHub 2.0 |
58
+ | ------------------ | -------------------- | --------------------------------- |
59
+ | **Infrastructure** | VM-based workers | Kubernetes pods |
60
+ | **Scaling** | vCore-based | Pod replicas + HPA |
61
+ | **Networking** | Shared Load Balancer | Ingress / private networking |
62
+ | **Persistence** | Object Store V1 | Object Store V2 (pod-local) |
63
+ | **Monitoring** | Anypoint Monitoring | Anypoint Monitoring + K8s metrics |
64
+
65
+ ### Runtime Fabric (RTF)
66
+
67
+ For hybrid or private cloud deployments:
68
+
69
+ - Self-managed Kubernetes clusters
70
+ - Air-gapped environments
71
+ - Regulatory compliance (data residency)
72
+
73
+ ---
74
+
75
+ ## Environment Promotion
76
+
77
+ ```
78
+ Development → QA → Staging → Production
79
+ ↓ ↓ ↓ ↓
80
+ dev.yaml qa.yaml stg.yaml prod.yaml
81
+ ```
82
+
83
+ ### Deployment Checklist
84
+
85
+ | Item | Description |
86
+ | ------------------------ | ----------------------------- |
87
+ | ✅ All tests pass | MUnit and integration tests |
88
+ | ✅ Lint checks pass | No errors from mule-lint |
89
+ | ✅ Properties configured | Environment YAML verified |
90
+ | ✅ Secrets encrypted | No plaintext credentials |
91
+ | ✅ API Manager policies | Authentication, rate limiting |
92
+ | ✅ Monitoring configured | Dashboards and alerts ready |
93
+ | ✅ Java 17 verified | Runtime compatible |
94
+
95
+ ---
96
+
97
+ ## API Governance at Design Time
98
+
99
+ Before publishing to Exchange, validate API specifications against organizational rulesets:
100
+
101
+ 1. Define governance rulesets in API Governance
102
+ 2. Validate RAML/OAS/AsyncAPI specs during design in ACB
103
+ 3. Block publishing to Exchange if governance rules fail
104
+ 4. Automate governance checks in CI/CD pipeline
105
+
106
+ ---
107
+
108
+ ## API Versioning
109
+
110
+ ### URL-Based Versioning (Recommended)
111
+
112
+ ```
113
+ /api/v1/orders
114
+ /api/v2/orders
115
+ ```
116
+
117
+ ### Deprecation Strategy
118
+
119
+ 1. Announce deprecation timeline to consumers
120
+ 2. Return `Deprecation` and `Sunset` headers
121
+ 3. Monitor v1 vs v2 adoption
122
+ 4. Sunset after consumer migration
123
+
124
+ ```xml
125
+ <set-variable variableName="outboundHeaders" value="#[{
126
+ 'Deprecation': 'true',
127
+ 'Sunset': 'Sat, 01 Jan 2027 00:00:00 GMT',
128
+ 'Link': '</api/v2/orders>; rel=\"successor-version\"'
129
+ }]"/>
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Monitoring & Observability
135
+
136
+ ### Three Pillars
137
+
138
+ | Pillar | Tool | Purpose |
139
+ | ----------- | ----------------------------------- | --------------------- |
140
+ | **Logs** | Anypoint Monitoring, Splunk, ELK | Debug, audit trail |
141
+ | **Metrics** | Anypoint Monitoring, Grafana | Performance, health |
142
+ | **Traces** | Anypoint Monitoring, Tracing module | Request flow, latency |
143
+
144
+ ### Key Metrics to Monitor
145
+
146
+ ```
147
+ Application Health:
148
+ ├── Response time (p50, p95, p99)
149
+ ├── Error rate (%)
150
+ ├── Throughput (requests/sec)
151
+ ├── Active connections
152
+ └── Worker CPU/Memory usage
153
+
154
+ Business Metrics:
155
+ ├── Records processed per hour
156
+ ├── Failed transactions
157
+ ├── API calls by consumer
158
+ └── Integration latency by backend
159
+ ```
160
+
161
+ ### Alerting
162
+
163
+ | Level | Condition | Response |
164
+ | ------------ | ------------------------------- | ----------------------- |
165
+ | **Critical** | Error rate > 10%, App down | Immediate on-call |
166
+ | **Warning** | Error rate > 5%, Latency > 5s | Investigate within 1h |
167
+ | **Info** | Resource > 70%, unusual pattern | Review in daily standup |
168
+
169
+ ---
170
+
171
+ **See also:** [CI/CD Integration](ci-cd.md) · [Security](security.md) · [Testing](testing.md)