sf-industry-commoncore-callable-apex

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

sf-industry-commoncore-callable-apex: Callable Apex for Salesforce Industries Common Core

sf-industry-commoncore-callable-apex: Salesforce Industries Common Core 专属Callable Apex

Specialist for Salesforce Industries Common Core callable Apex implementations. Produce secure, deterministic, and configurable Apex that cleanly integrates with OmniStudio and Industries extension points.
专注于Salesforce Industries Common Core的Callable Apex实现。生成安全、可预测、可配置的Apex代码,与OmniStudio及Industries扩展点无缝集成。

Core Responsibilities

核心职责

  1. Callable Generation: Build
    System.Callable
    classes with safe action dispatch
  2. Callable Review: Audit existing callable implementations for correctness and risks
  3. Validation & Scoring: Evaluate against the 120-point rubric
  4. Industries Fit: Ensure compatibility with OmniStudio/Industries extension points

  1. Callable生成:构建具备安全动作分发机制的
    System.Callable
  2. Callable审查:审核现有Callable实现的正确性与风险点
  3. 验证与评分:基于120分评估标准进行合规性检查
  4. Industries适配:确保与OmniStudio/Industries扩展点的兼容性

Workflow (4-Phase Pattern)

工作流程(四阶段模式)

Phase 1: Requirements Gathering

阶段1:需求收集

Ask for:
  • Entry point (OmniScript, Integration Procedure, DataRaptor, or other Industries hook)
  • Action names (strings passed into
    call
    )
  • Input/output contract (required keys, types, and response shape)
  • Data access needs (objects/fields, CRUD/FLS rules)
  • Side effects (DML, callouts, async requirements)
Then:
  1. Scan for existing callable classes:
    Glob: **/*Callable*.cls
  2. Identify shared utilities or base classes used for Industries extensions
  3. Create a task list

请用户提供:
  • 入口点(OmniScript、Integration Procedure、DataRaptor或其他Industries钩子)
  • 动作名称(传入
    call
    方法的字符串)
  • 输入/输出契约(必填键、类型及响应结构)
  • 数据访问需求(对象/字段、CRUD/FLS规则)
  • 副作用(DML操作、外部调用、异步需求)
完成需求收集后:
  1. 扫描现有Callable类:
    Glob: **/*Callable*.cls
  2. 识别用于Industries扩展的共享工具类或基类
  3. 创建任务清单

Phase 2: Design & Contract Definition

阶段2:设计与契约定义

Define the callable contract:
  • Action list (explicit, versioned strings)
  • Input schema (required keys + types)
  • Output schema (consistent response envelope)
Recommended response envelope:
{
  "success": true|false,
  "data": {...},
  "errors": [ { "code": "...", "message": "..." } ]
}
Action dispatch rules:
  • Use
    switch on action
  • Default case throws a typed exception
  • No dynamic method invocation or reflection
VlocityOpenInterface / VlocityOpenInterface2 contract mapping:
When designing for legacy Open Interface extensions (or dual Callable + Open Interface support), map the signature:
invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options)
ParameterRoleCallable equivalent
methodName
Action selector (same semantics as
action
)
action
in
call(action, args)
inputMap
Primary input data (required keys, types)
args.get('inputMap')
outputMap
Mutable map where results are written (out-by-reference)Return value; Callable returns envelope instead
options
Additional context (parent DataRaptor/OmniScript context, invocation metadata)
args.get('options')
Design rules for Open Interface contracts:
  • Treat
    inputMap
    and
    options
    as the combined input schema
  • Define what keys must be written to
    outputMap
    per action (success and error cases)
  • Preserve
    methodName
    strings so they align with Callable
    action
    strings
  • Document whether
    options
    is required, optional, or unused for each action

定义Callable契约
  • 动作列表(明确的、带版本的字符串)
  • 输入 schema(必填键+类型)
  • 输出 schema(统一的响应信封结构)
推荐响应信封结构
{
  "success": true|false,
  "data": {...},
  "errors": [ { "code": "...", "message": "..." } ]
}
动作分发规则
  • 使用
    switch on action
    语法
  • 默认分支抛出类型化异常
  • 禁止使用动态方法调用或反射
VlocityOpenInterface / VlocityOpenInterface2契约映射
当为遗留Open Interface扩展设计(或同时支持Callable + Open Interface)时,需映射以下签名:
invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options)
参数作用Callable等效项
methodName
动作选择器(与
action
语义一致)
call(action, args)
中的
action
inputMap
主要输入数据(必填键、类型)
args.get('inputMap')
outputMap
写入结果的可变映射(按引用传递输出)返回值;Callable直接返回响应信封
options
附加上下文(父级DataRaptor/OmniScript上下文、调用元数据)
args.get('options')
Open Interface契约设计规则:
  • inputMap
    options
    视为组合输入schema
  • 定义每个动作需写入
    outputMap
    的键(成功与错误场景)
  • 保持
    methodName
    字符串与Callable的
    action
    字符串一致
  • 记录每个动作中
    options
    是必填、可选还是未使用

Phase 3: Implementation Pattern

阶段3:实现模式

Vanilla System.Callable (flat args, no Open Interface coupling):
apex
public with sharing class Industries_OrderCallable implements System.Callable {
    public Object call(String action, Map<String, Object> args) {
        switch on action {
            when 'createOrder' {
                return createOrder(args != null ? args : new Map<String, Object>());
            }
            when else {
                throw new IndustriesCallableException('Unsupported action: ' + action);
            }
        }
    }

    private Map<String, Object> createOrder(Map<String, Object> args) {
        // Validate input (e.g. args.get('orderId')), run business logic, return response envelope
        return new Map<String, Object>{ 'success' => true };
    }
}
Use the vanilla pattern when callers pass flat args and no VlocityOpenInterface integration is required.
Callable skeleton (same inputs as VlocityOpenInterface):
Use
inputMap
and
options
keys in
args
when integrating with Open Interface or when callers pass that structure:
apex
public with sharing class Industries_OrderCallable implements System.Callable {
    public Object call(String action, Map<String, Object> args) {
        Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
            ? (Map<String, Object>) args.get('inputMap') : (args != null ? args : new Map<String, Object>());
        Map<String, Object> options  = (args != null && args.containsKey('options'))
            ? (Map<String, Object>) args.get('options')  : new Map<String, Object>();
        if (inputMap == null) { inputMap = new Map<String, Object>(); }
        if (options  == null) { options  = new Map<String, Object>(); }

        switch on action {
            when 'createOrder' {
                return createOrder(inputMap, options);
            }
            when else {
                throw new IndustriesCallableException('Unsupported action: ' + action);
            }
        }
    }

    private Map<String, Object> createOrder(Map<String, Object> inputMap, Map<String, Object> options) {
        // Validate input, run business logic, return response envelope
        return new Map<String, Object>{ 'success' => true };
    }
}
Input format: Callers pass
args
as
{ 'inputMap' => Map<String, Object>, 'options' => Map<String, Object> }
. For backward compatibility with flat callers, if
args
lacks
'inputMap'
, treat
args
itself as
inputMap
and use an empty map for
options
.
Implementation rules:
  1. Keep
    call()
    thin; delegate to private methods or service classes
  2. Validate and coerce input types early (null-safe)
  3. Enforce CRUD/FLS and sharing (
    with sharing
    ,
    Security.stripInaccessible()
    )
  4. Bulkify when args include record collections
  5. Use
    WITH USER_MODE
    for SOQL when appropriate
VlocityOpenInterface / VlocityOpenInterface2 implementation:
When implementing
omnistudio.VlocityOpenInterface
or
omnistudio.VlocityOpenInterface2
, use the signature:
apex
global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
                           Map<String, Object> outputMap, Map<String, Object> options)
Open Interface skeleton:
apex
global with sharing class Industries_OrderOpenInterface implements omnistudio.VlocityOpenInterface2 {
    global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
                                Map<String, Object> outputMap, Map<String, Object> options) {
        switch on methodName {
            when 'createOrder' {
                Map<String, Object> result = createOrder(inputMap, options);
                outputMap.putAll(result);
                return true;
            }
            when else {
                outputMap.put('success', false);
                outputMap.put('errors', new List<Map<String, Object>>{
                    new Map<String, Object>{ 'code' => 'UNSUPPORTED_ACTION', 'message' => 'Unsupported action: ' + methodName }
                });
                return false;
            }
        }
    }

    private Map<String, Object> createOrder(Map<String, Object> inputMap, Map<String, Object> options) {
        // Validate input, run business logic, return response envelope
        return new Map<String, Object>{ 'success' => true, 'data' => new Map<String, Object>() };
    }
}
Open Interface implementation rules:
  • Write results into
    outputMap
    via
    putAll()
    or individual
    put()
    calls; do not return the envelope from
    invokeMethod
  • Return
    true
    for success,
    false
    for unsupported or failed actions
  • Use the same internal private methods as the Callable (same
    inputMap
    and
    options
    parameters); only the entry point differs
  • Populate
    outputMap
    with the same envelope shape (
    success
    ,
    data
    ,
    errors
    ) for consistency
Both Callable and Open Interface accept the same inputs (
inputMap
,
options
) and delegate to identical private method signatures for shared logic.

原生System.Callable(扁平参数,无Open Interface耦合):
apex
public with sharing class Industries_OrderCallable implements System.Callable {
    public Object call(String action, Map<String, Object> args) {
        switch on action {
            when 'createOrder' {
                return createOrder(args != null ? args : new Map<String, Object>());
            }
            when else {
                throw new IndustriesCallableException('Unsupported action: ' + action);
            }
        }
    }

    private Map<String, Object> createOrder(Map<String, Object> args) {
        // 验证输入(如args.get('orderId'))、执行业务逻辑、返回响应信封
        return new Map<String, Object>{ 'success' => true };
    }
}
当调用方传递扁平参数且无需集成VlocityOpenInterface时,使用原生模式。
Callable骨架(与VlocityOpenInterface输入一致):
当与Open Interface集成,或调用方传递该结构时,在
args
中使用
inputMap
options
键:
apex
public with sharing class Industries_OrderCallable implements System.Callable {
    public Object call(String action, Map<String, Object> args) {
        Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
            ? (Map<String, Object>) args.get('inputMap') : (args != null ? args : new Map<String, Object>());
        Map<String, Object> options  = (args != null && args.containsKey('options'))
            ? (Map<String, Object>) args.get('options')  : new Map<String, Object>();
        if (inputMap == null) { inputMap = new Map<String, Object>(); }
        if (options  == null) { options  = new Map<String, Object>(); }

        switch on action {
            when 'createOrder' {
                return createOrder(inputMap, options);
            }
            when else {
                throw new IndustriesCallableException('Unsupported action: ' + action);
            }
        }
    }

    private Map<String, Object> createOrder(Map<String, Object> inputMap, Map<String, Object> options) {
        // 验证输入、执行业务逻辑、返回响应信封
        return new Map<String, Object>{ 'success' => true };
    }
}
输入格式:调用方传递
args
{ 'inputMap' => Map<String, Object>, 'options' => Map<String, Object> }
。为兼容扁平参数的调用方,若
args
不含
'inputMap'
,则将
args
本身视为
inputMap
,并使用空映射作为
options
实现规则
  1. 保持
    call()
    方法简洁;将业务逻辑委托给私有方法或服务类
  2. 尽早验证并强制转换输入类型(空值安全)
  3. 强制执行CRUD/FLS与共享规则(
    with sharing
    Security.stripInaccessible()
  4. 当参数包含记录集合时,需支持批量处理
  5. 适当为SOQL使用
    WITH USER_MODE
VlocityOpenInterface / VlocityOpenInterface2实现
当实现
omnistudio.VlocityOpenInterface
omnistudio.VlocityOpenInterface2
时,使用以下签名:
apex
global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
                           Map<String, Object> outputMap, Map<String, Object> options)
Open Interface骨架:
apex
global with sharing class Industries_OrderOpenInterface implements omnistudio.VlocityOpenInterface2 {
    global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
                                Map<String, Object> outputMap, Map<String, Object> options) {
        switch on methodName {
            when 'createOrder' {
                Map<String, Object> result = createOrder(inputMap, options);
                outputMap.putAll(result);
                return true;
            }
            when else {
                outputMap.put('success', false);
                outputMap.put('errors', new List<Map<String, Object>>{
                    new Map<String, Object>{ 'code' => 'UNSUPPORTED_ACTION', 'message' => 'Unsupported action: ' + methodName }
                });
                return false;
            }
        }
    }

    private Map<String, Object> createOrder(Map<String, Object> inputMap, Map<String, Object> options) {
        // 验证输入、执行业务逻辑、返回响应信封
        return new Map<String, Object>{ 'success' => true, 'data' => new Map<String, Object>() };
    }
}
Open Interface实现规则:
  • 通过
    putAll()
    或单个
    put()
    调用将结果写入
    outputMap
    ;禁止从
    invokeMethod
    返回响应信封
  • 成功返回
    true
    ,不支持或失败的动作返回
    false
  • 使用与Callable相同的内部私有方法(相同的
    inputMap
    options
    参数);仅入口点不同
  • 为保证一致性,使用相同的信封结构(
    success
    data
    errors
    )填充
    outputMap
Callable与Open Interface接受相同的输入(
inputMap
options
),并委托给相同签名的私有方法以实现逻辑共享。

Phase 4: Testing & Validation

阶段4:测试与验证

Minimum tests:
  • Positive: Supported action executes successfully
  • Negative: Unsupported action throws expected exception
  • Contract: Missing/invalid inputs return error envelope
  • Bulk: Handles list inputs without hitting limits
Example test class:
apex
@IsTest
private class Industries_OrderCallableTest {
    @IsTest
    static void testCreateOrder() {
        System.Callable svc = new Industries_OrderCallable();
        Map<String, Object> args = new Map<String, Object>{
            'inputMap' => new Map<String, Object>{ 'orderId' => '001000000000001' },
            'options'  => new Map<String, Object>()
        };
        Map<String, Object> result =
            (Map<String, Object>) svc.call('createOrder', args);
        Assert.isTrue((Boolean) result.get('success'));
    }

    @IsTest
    static void testUnsupportedAction() {
        try {
            System.Callable svc = new Industries_OrderCallable();
            svc.call('unknownAction', new Map<String, Object>());
            Assert.fail('Expected IndustriesCallableException');
        } catch (IndustriesCallableException e) {
            Assert.isTrue(e.getMessage().contains('Unsupported action'));
        }
    }
}

最低测试要求:
  • 正向测试:支持的动作可成功执行
  • 反向测试:不支持的动作抛出预期异常
  • 契约测试:缺失/无效输入返回错误信封
  • 批量测试:处理列表输入时不触发平台限制
示例测试类
apex
@IsTest
private class Industries_OrderCallableTest {
    @IsTest
    static void testCreateOrder() {
        System.Callable svc = new Industries_OrderCallable();
        Map<String, Object> args = new Map<String, Object>{
            'inputMap' => new Map<String, Object>{ 'orderId' => '001000000000001' },
            'options'  => new Map<String, Object>()
        };
        Map<String, Object> result =
            (Map<String, Object>) svc.call('createOrder', args);
        Assert.isTrue((Boolean) result.get('success'));
    }

    @IsTest
    static void testUnsupportedAction() {
        try {
            System.Callable svc = new Industries_OrderCallable();
            svc.call('unknownAction', new Map<String, Object>());
            Assert.fail('Expected IndustriesCallableException');
        } catch (IndustriesCallableException e) {
            Assert.isTrue(e.getMessage().contains('Unsupported action'));
        }
    }
}

Migration: VlocityOpenInterface to System.Callable

迁移:从VlocityOpenInterface到System.Callable

When modernizing Industries extensions, move
VlocityOpenInterface
or
VlocityOpenInterface2
implementations to
System.Callable
and keep the action contract stable. Use the Salesforce guidance as the source of truth. Salesforce Help
Guidance:
  • Preserve action names (
    methodName
    ) as
    action
    strings in
    call()
  • Pass
    inputMap
    and
    options
    as keys in
    args
    :
    { 'inputMap' => inputMap, 'options' => options }
  • Return a consistent response envelope instead of mutating
    outMap
  • Keep
    call()
    thin; delegate to the same internal methods with
    (inputMap, options)
    signature
  • Add tests for each action and unsupported action
Example migration (pattern):
apex
// BEFORE: VlocityOpenInterface2
global class OrderOpenInterface implements omnistudio.VlocityOpenInterface2 {
    global Boolean invokeMethod(String methodName, Map<String, Object> input,
                                Map<String, Object> output,
                                Map<String, Object> options) {
        if (methodName == 'createOrder') {
            output.putAll(createOrder(input, options));
            return true;
        }
        return false;
    }
}

// AFTER: System.Callable (same inputs: inputMap, options)
public with sharing class OrderCallable implements System.Callable {
    public Object call(String action, Map<String, Object> args) {
        Map<String, Object> inputMap = args != null ? (Map<String, Object>) args.get('inputMap') : new Map<String, Object>();
        Map<String, Object> options  = args != null ? (Map<String, Object>) args.get('options')   : new Map<String, Object>();
        if (inputMap == null) { inputMap = new Map<String, Object>(); }
        if (options  == null) { options  = new Map<String, Object>(); }

        switch on action {
            when 'createOrder' {
                return createOrder(inputMap, options);
            }
            when else {
                throw new IndustriesCallableException('Unsupported action: ' + action);
            }
        }
    }
}

在现代化Industries扩展时,将
VlocityOpenInterface
VlocityOpenInterface2
实现迁移至
System.Callable
,并保持动作契约稳定。请以Salesforce官方指南为权威依据。 Salesforce帮助文档
迁移指南
  • 保留动作名称(
    methodName
    )作为
    call()
    中的
    action
    字符串
  • inputMap
    options
    作为
    args
    中的键传递:
    { 'inputMap' => inputMap, 'options' => options }
  • 返回统一的响应信封,而非修改
    outMap
  • 保持
    call()
    方法简洁;委托给相同签名的内部方法
    (inputMap, options)
  • 为每个动作及不支持的动作添加测试
迁移示例(模式)
apex
// 迁移前:VlocityOpenInterface2
global class OrderOpenInterface implements omnistudio.VlocityOpenInterface2 {
    global Boolean invokeMethod(String methodName, Map<String, Object> input,
                                Map<String, Object> output,
                                Map<String, Object> options) {
        if (methodName == 'createOrder') {
            output.putAll(createOrder(input, options));
            return true;
        }
        return false;
    }
}

// 迁移后:System.Callable(相同输入:inputMap, options)
public with sharing class OrderCallable implements System.Callable {
    public Object call(String action, Map<String, Object> args) {
        Map<String, Object> inputMap = args != null ? (Map<String, Object>) args.get('inputMap') : new Map<String, Object>();
        Map<String, Object> options  = args != null ? (Map<String, Object>) args.get('options')   : new Map<String, Object>();
        if (inputMap == null) { inputMap = new Map<String, Object>(); }
        if (options  == null) { options  = new Map<String, Object>(); }

        switch on action {
            when 'createOrder' {
                return createOrder(inputMap, options);
            }
            when else {
                throw new IndustriesCallableException('Unsupported action: ' + action);
            }
        }
    }
}

Best Practices (120-Point Scoring)

最佳实践(120分制评分)

CategoryPointsKey Rules
Contract & Dispatch20Explicit action list;
switch on
; versioned action strings
Input Validation20Required keys validated; types coerced safely; null guards
Security20
with sharing
; CRUD/FLS checks;
Security.stripInaccessible()
Error Handling15Typed exceptions; consistent error envelope; no empty catch
Bulkification & Limits20No SOQL/DML in loops; supports list inputs
Testing15Positive/negative/contract/bulk tests
Documentation10ApexDoc for class and action methods
Thresholds: ✅ 90+ (Ready) | ⚠️ 70-89 (Review) | ❌ <70 (Block)

分类分值核心规则
契约与分发20明确的动作列表;使用
switch on
;带版本的动作字符串
输入验证20验证必填键;安全强制转换类型;空值防护
安全性20使用
with sharing
;执行CRUD/FLS检查;使用
Security.stripInaccessible()
错误处理15类型化异常;统一错误信封;禁止空catch块
批量处理与限制20循环中禁止SOQL/DML;支持列表输入
测试15正向/反向/契约/批量测试
文档10为类和动作方法添加ApexDoc注释
阈值:✅ 90分及以上(就绪) | ⚠️ 70-89分(需审查) | ❌ 70分以下(阻塞)

⛔ Guardrails (Mandatory)

⛔ 防护规则(强制)

Stop and ask the user if any of these would be introduced:
  • Dynamic method execution based on user input (no reflection)
  • SOQL/DML inside loops
  • without sharing
    on callable classes
  • Silent failures (empty catch, swallowed exceptions)
  • Inconsistent response shapes across actions

若将引入以下任何情况,请暂停并询问用户:
  • 基于用户输入的动态方法执行(禁止反射)
  • 循环内的SOQL/DML操作
  • Callable类使用
    without sharing
  • 静默失败(空catch块、吞掉异常)
  • 动作间响应结构不一致

Common Anti-Patterns

常见反模式

  • call()
    contains business logic instead of delegating
  • Action names are unversioned or not documented
  • Input maps assumed to have keys without checks
  • Mixed response types (sometimes Map, sometimes String)
  • No tests for unsupported actions

  • call()
    方法包含业务逻辑而非仅做委托
  • 动作名称未带版本或未文档化
  • 假设输入映射包含特定键而未做检查
  • 混合响应类型(有时返回Map,有时返回String)
  • 未为不支持的动作编写测试

Cross-Skill Integration

跨技能集成

SkillWhen to UseExample
sf-apexGeneral Apex work beyond callable implementations"Create trigger for Account"
sf-metadataVerify object/field availability before coding"Describe Product2"
sf-deployValidate/deploy callable classes"Deploy to sandbox"

技能使用场景示例
sf-apex超出Callable实现的通用Apex开发工作"为Account对象创建触发器"
sf-metadata编码前验证对象/字段的可用性"描述Product2对象"
sf-deploy验证/部署Callable类"部署至沙箱环境"

Reference Skill

参考技能

Use the core Apex standards, testing patterns, and guardrails in:
  • skills/sf-apex/SKILL.md

请遵循核心Apex标准、测试模式及防护规则,详见:
  • skills/sf-apex/SKILL.md

Bundled Examples

捆绑示例

  • examples/Test_QuoteByProductCallable/ — read-only query example with
    WITH USER_MODE
  • examples/Test_VlocityOpenInterfaceConversion/ — migration from legacy
    VlocityOpenInterface
  • examples/Test_VlocityOpenInterface2Conversion/ — migration from
    VlocityOpenInterface2
  • examples/Test_QuoteByProductCallable/ — 带
    WITH USER_MODE
    的只读查询示例
  • examples/Test_VlocityOpenInterfaceConversion/ — 从遗留
    VlocityOpenInterface
    迁移的示例
  • examples/Test_VlocityOpenInterface2Conversion/ — 从
    VlocityOpenInterface2
    迁移的示例

Notes

注意事项

  • Prefer deterministic, side-effect-aware callable actions
  • Keep action contracts stable; introduce new actions for breaking changes
  • Avoid long-running work in synchronous callables; use async when needed

  • 优先选择可预测、无意外副作用的Callable动作
  • 保持动作契约稳定;若需破坏性变更,请新增动作
  • 同步Callable中避免长时间运行的任务;必要时使用异步处理