mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 03:09:50 +08:00
278 lines
7.4 KiB
Markdown
278 lines
7.4 KiB
Markdown
<!-- Parent: omnistudio-datamapper-generate/SKILL.md -->
|
|
|
|
# Data Mapper Best Practices
|
|
|
|
## When to Use Each Type
|
|
|
|
### Extract
|
|
|
|
Use Extract when:
|
|
- Reading data from one or more related Salesforce objects
|
|
- You need relationship queries (parent-to-child or child-to-parent)
|
|
- Formula fields are required in the output
|
|
- Data volume is moderate (under 10K records per execution)
|
|
- You need aggregate functions or complex filter logic
|
|
|
|
Do NOT use Extract when:
|
|
- Read volume exceeds 10K records consistently -- use Turbo Extract
|
|
- No SOQL is needed (data reshaping only) -- use Transform
|
|
- You are writing data -- use Load
|
|
|
|
### Turbo Extract
|
|
|
|
Use Turbo Extract when:
|
|
- Read-heavy scenarios with high volume (10K+ records)
|
|
- Query is straightforward with indexed filter fields
|
|
- Formula fields are not required
|
|
- No related list (child-to-parent) queries are needed
|
|
- Performance is the primary concern (10x+ faster than standard Extract)
|
|
|
|
Do NOT use Turbo Extract when:
|
|
- Formula fields are needed in output
|
|
- Related list queries are required
|
|
- Aggregate queries (COUNT, SUM) are needed
|
|
- Polymorphic lookup fields are involved
|
|
|
|
### Transform
|
|
|
|
Use Transform when:
|
|
- Reshaping JSON structures between Integration Procedure steps
|
|
- Renaming fields from one schema to another
|
|
- Flattening nested data structures
|
|
- Filtering or merging in-memory datasets
|
|
- No database interaction is needed
|
|
|
|
Do NOT use Transform when:
|
|
- Reading from or writing to Salesforce objects -- use Extract or Load
|
|
- Data needs to be persisted -- use Load
|
|
|
|
### Load
|
|
|
|
Use Load when:
|
|
- Inserting, updating, upserting, or deleting Salesforce records
|
|
- Writing data collected from OmniScript input or external sources
|
|
- Synchronizing data from Integration Procedure callout responses
|
|
|
|
Do NOT use Load when:
|
|
- Reading data -- use Extract or Turbo Extract
|
|
- Reshaping data without persistence -- use Transform
|
|
|
|
---
|
|
|
|
## Field Mapping Patterns
|
|
|
|
### Explicit Field Selection
|
|
|
|
Always specify fields explicitly. Never rely on wildcard or "all fields" selection.
|
|
|
|
```
|
|
Good: Account.Name, Account.Industry, Account.BillingCity
|
|
Bad: Account.* (extracts all fields, wastes bandwidth and heap)
|
|
```
|
|
|
|
### Input/Output Path Structure
|
|
|
|
Data Mapper fields use dot-notation paths for input and output:
|
|
|
|
```
|
|
Input Path: AccountData.Name
|
|
Output Path: Account.Name
|
|
```
|
|
|
|
For nested structures:
|
|
|
|
```
|
|
Input Path: Response.data.accounts[0].name
|
|
Output Path: AccountList.Name
|
|
```
|
|
|
|
### Type Conversion Handling
|
|
|
|
Map fields with compatible types. Common conversions:
|
|
|
|
| Source Type | Target Type | Notes |
|
|
|-------------|-------------|-------|
|
|
| String | Date | Requires ISO 8601 format (YYYY-MM-DD) |
|
|
| String | Number | Ensure source contains numeric values only |
|
|
| Boolean | String | Maps to "true"/"false" string literals |
|
|
| DateTime | Date | Truncates time component |
|
|
| Picklist | String | Maps selected value as string |
|
|
|
|
### Lookup Resolution for Load
|
|
|
|
When loading records with lookup relationships:
|
|
|
|
1. Define a lookup mapping that resolves the external key to a Salesforce ID
|
|
2. Specify the lookup object and match field (e.g., `Account.Name` to resolve `AccountId`)
|
|
3. Handle cases where the lookup returns no match (set default or fail gracefully)
|
|
|
|
```
|
|
Field: AccountId
|
|
Lookup Object: Account
|
|
Match Field: Name
|
|
Input Path: InputData.AccountName
|
|
```
|
|
|
|
---
|
|
|
|
## Query Sequence Optimization
|
|
|
|
### Filter Field Indexing
|
|
|
|
For Extract and Turbo Extract, filter on indexed fields whenever possible:
|
|
|
|
- `Id` (always indexed)
|
|
- `Name` (indexed on standard objects)
|
|
- `CreatedDate` (indexed)
|
|
- `SystemModstamp` (indexed)
|
|
- Custom fields marked as External ID or Unique
|
|
- Custom Index fields (request from admin if needed)
|
|
|
|
### Filter Order
|
|
|
|
Place the most selective filter first to reduce the result set early:
|
|
|
|
```
|
|
Good: WHERE Id = :recordId AND Status = 'Active'
|
|
Bad: WHERE Status = 'Active' AND Id = :recordId
|
|
```
|
|
|
|
### Relationship Query Depth
|
|
|
|
Limit relationship traversal to 2 levels for performance:
|
|
|
|
```
|
|
Good: Account.Owner.Name (1 level)
|
|
Bad: Account.Parent.Parent.Parent.Owner.Name (3+ levels)
|
|
```
|
|
|
|
### Limit and Offset
|
|
|
|
Always set a LIMIT on Extract queries unless the consuming component guarantees a bounded input:
|
|
|
|
```
|
|
LIMIT 200 -- Standard batch size
|
|
LIMIT 2000 -- Maximum for most UI scenarios
|
|
LIMIT 10000 -- Absolute maximum, use only with pagination
|
|
```
|
|
|
|
---
|
|
|
|
## Null Handling and Type Conversion
|
|
|
|
### Default Values
|
|
|
|
Set default values for fields that may be null to prevent downstream errors:
|
|
|
|
| Field Type | Recommended Default | Rationale |
|
|
|------------|-------------------|-----------|
|
|
| String | `""` (empty string) | Prevents null reference in concatenation |
|
|
| Number | `0` | Prevents null arithmetic errors |
|
|
| Boolean | `false` | Prevents null conditional evaluation |
|
|
| Date | (no default) | Leave null; do not fabricate dates |
|
|
| Lookup | (no default) | Leave null; handle in consuming component |
|
|
|
|
### Null-Safe Mapping
|
|
|
|
When mapping fields that may be null:
|
|
|
|
1. Set `isNullable: true` on the OmniDataTransformItem
|
|
2. Configure a default value where business logic requires one
|
|
3. Document which fields are expected to be null in certain scenarios
|
|
4. Test with records that have null values in mapped fields
|
|
|
|
---
|
|
|
|
## Relationship Queries
|
|
|
|
### Parent-to-Child (Subquery)
|
|
|
|
Extract supports parent-to-child relationship queries:
|
|
|
|
```
|
|
Object: Account
|
|
Fields: Name, Industry
|
|
Child Relationship: Contacts (Contact)
|
|
Child Fields: FirstName, LastName, Email
|
|
```
|
|
|
|
Output structure:
|
|
|
|
```json
|
|
{
|
|
"Name": "Acme Corp",
|
|
"Industry": "Technology",
|
|
"Contacts": [
|
|
{ "FirstName": "Jane", "LastName": "Doe", "Email": "jane@acme.com" }
|
|
]
|
|
}
|
|
```
|
|
|
|
### Child-to-Parent (Lookup Traversal)
|
|
|
|
Extract supports child-to-parent traversal via dot notation:
|
|
|
|
```
|
|
Object: Contact
|
|
Fields: FirstName, LastName, Account.Name, Account.Industry
|
|
```
|
|
|
|
Output structure:
|
|
|
|
```json
|
|
{
|
|
"FirstName": "Jane",
|
|
"LastName": "Doe",
|
|
"AccountName": "Acme Corp",
|
|
"AccountIndustry": "Technology"
|
|
}
|
|
```
|
|
|
|
### Turbo Extract Limitations
|
|
|
|
Turbo Extract does NOT support:
|
|
- Child-to-parent relationship queries (subqueries)
|
|
- Formula fields
|
|
- Aggregate functions
|
|
- Polymorphic lookups (e.g., `WhoId` on Task)
|
|
|
|
---
|
|
|
|
## Performance with Large Data Volumes
|
|
|
|
### Batch Size Recommendations
|
|
|
|
| Record Count | Recommended Approach |
|
|
|--------------|---------------------|
|
|
| 1-200 | Standard Extract, single call |
|
|
| 200-2000 | Turbo Extract, single call |
|
|
| 2000-10000 | Turbo Extract with pagination via Integration Procedure |
|
|
| 10000+ | Batch processing: scheduled Integration Procedure with chunked Turbo Extract calls |
|
|
|
|
### Heap Size Management
|
|
|
|
Each Data Mapper execution contributes to the Apex heap limit (6 MB synchronous, 12 MB async):
|
|
|
|
- Map only the fields you need (reduces JSON payload size)
|
|
- Use Turbo Extract for large result sets (server-side processing reduces heap usage)
|
|
- Paginate results when total data exceeds 2 MB
|
|
|
|
### Caching Strategy
|
|
|
|
For data that changes infrequently:
|
|
|
|
- Enable Platform Cache in the consuming Integration Procedure
|
|
- Set appropriate TTL (Time To Live) based on data change frequency
|
|
- Cache at the org partition level for shared reference data
|
|
- Cache at the session partition level for user-specific data
|
|
|
|
### Monitoring
|
|
|
|
Track Data Mapper performance using:
|
|
|
|
```bash
|
|
sf data query -q "SELECT Id,Name,Type,LastModifiedDate FROM OmniDataTransform WHERE IsActive=true ORDER BY LastModifiedDate DESC" -o <org>
|
|
```
|
|
|
|
Review execution logs in OmniStudio Designer > Data Mapper > Preview to identify slow queries and excessive field counts.
|