sf-industry-commoncore-callable-apex
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesesf-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
核心职责
- Callable Generation: Build classes with safe action dispatch
System.Callable - Callable Review: Audit existing callable implementations for correctness and risks
- Validation & Scoring: Evaluate against the 120-point rubric
- Industries Fit: Ensure compatibility with OmniStudio/Industries extension points
- Callable生成:构建具备安全动作分发机制的类
System.Callable - Callable审查:审核现有Callable实现的正确性与风险点
- 验证与评分:基于120分评估标准进行合规性检查
- 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:
- Scan for existing callable classes:
Glob: **/*Callable*.cls - Identify shared utilities or base classes used for Industries extensions
- Create a task list
请用户提供:
- 入口点(OmniScript、Integration Procedure、DataRaptor或其他Industries钩子)
- 动作名称(传入方法的字符串)
call - 输入/输出契约(必填键、类型及响应结构)
- 数据访问需求(对象/字段、CRUD/FLS规则)
- 副作用(DML操作、外部调用、异步需求)
完成需求收集后:
- 扫描现有Callable类:
Glob: **/*Callable*.cls - 识别用于Industries扩展的共享工具类或基类
- 创建任务清单
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)| Parameter | Role | Callable equivalent |
|---|---|---|
| Action selector (same semantics as | |
| Primary input data (required keys, types) | |
| Mutable map where results are written (out-by-reference) | Return value; Callable returns envelope instead |
| Additional context (parent DataRaptor/OmniScript context, invocation metadata) | |
Design rules for Open Interface contracts:
- Treat and
inputMapas the combined input schemaoptions - Define what keys must be written to per action (success and error cases)
outputMap - Preserve strings so they align with Callable
methodNamestringsaction - Document whether is required, optional, or unused for each action
options
定义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等效项 |
|---|---|---|
| 动作选择器(与 | |
| 主要输入数据(必填键、类型) | |
| 写入结果的可变映射(按引用传递输出) | 返回值;Callable直接返回响应信封 |
| 附加上下文(父级DataRaptor/OmniScript上下文、调用元数据) | |
Open Interface契约设计规则:
- 将和
inputMap视为组合输入schemaoptions - 定义每个动作需写入的键(成功与错误场景)
outputMap - 保持字符串与Callable的
methodName字符串一致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 and keys in when integrating with Open Interface or when callers pass that structure:
inputMapoptionsargsapex
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 as . For backward compatibility with flat callers, if lacks , treat itself as and use an empty map for .
args{ 'inputMap' => Map<String, Object>, 'options' => Map<String, Object> }args'inputMap'argsinputMapoptionsImplementation rules:
- Keep thin; delegate to private methods or service classes
call() - Validate and coerce input types early (null-safe)
- Enforce CRUD/FLS and sharing (,
with sharing)Security.stripInaccessible() - Bulkify when args include record collections
- Use for SOQL when appropriate
WITH USER_MODE
VlocityOpenInterface / VlocityOpenInterface2 implementation:
When implementing or , use the signature:
omnistudio.VlocityOpenInterfaceomnistudio.VlocityOpenInterface2apex
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 via
outputMapor individualputAll()calls; do not return the envelope fromput()invokeMethod - Return for success,
truefor unsupported or failed actionsfalse - Use the same internal private methods as the Callable (same and
inputMapparameters); only the entry point differsoptions - Populate with the same envelope shape (
outputMap,success,data) for consistencyerrors
Both Callable and Open Interface accept the same inputs (, ) and delegate to identical private method signatures for shared logic.
inputMapoptions原生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集成,或调用方传递该结构时,在中使用和键:
argsinputMapoptionsapex
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'argsinputMapoptions实现规则:
- 保持方法简洁;将业务逻辑委托给私有方法或服务类
call() - 尽早验证并强制转换输入类型(空值安全)
- 强制执行CRUD/FLS与共享规则(、
with sharing)Security.stripInaccessible() - 当参数包含记录集合时,需支持批量处理
- 适当为SOQL使用
WITH USER_MODE
VlocityOpenInterface / VlocityOpenInterface2实现:
当实现或时,使用以下签名:
omnistudio.VlocityOpenInterfaceomnistudio.VlocityOpenInterface2apex
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 - 成功返回,不支持或失败的动作返回
truefalse - 使用与Callable相同的内部私有方法(相同的和
inputMap参数);仅入口点不同options - 为保证一致性,使用相同的信封结构(、
success、data)填充errorsoutputMap
Callable与Open Interface接受相同的输入(、),并委托给相同签名的私有方法以实现逻辑共享。
inputMapoptionsPhase 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 or
implementations to and keep the
action contract stable. Use the Salesforce guidance as the source of truth.
Salesforce Help
VlocityOpenInterfaceVlocityOpenInterface2System.CallableGuidance:
- Preserve action names () as
methodNamestrings inactioncall() - Pass and
inputMapas keys inoptions:args{ 'inputMap' => inputMap, 'options' => options } - Return a consistent response envelope instead of mutating
outMap - Keep thin; delegate to the same internal methods with
call()signature(inputMap, options) - 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扩展时,将或实现迁移至,并保持动作契约稳定。请以Salesforce官方指南为权威依据。
Salesforce帮助文档
VlocityOpenInterfaceVlocityOpenInterface2System.Callable迁移指南:
- 保留动作名称()作为
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分制评分)
| Category | Points | Key Rules |
|---|---|---|
| Contract & Dispatch | 20 | Explicit action list; |
| Input Validation | 20 | Required keys validated; types coerced safely; null guards |
| Security | 20 | |
| Error Handling | 15 | Typed exceptions; consistent error envelope; no empty catch |
| Bulkification & Limits | 20 | No SOQL/DML in loops; supports list inputs |
| Testing | 15 | Positive/negative/contract/bulk tests |
| Documentation | 10 | ApexDoc for class and action methods |
Thresholds: ✅ 90+ (Ready) | ⚠️ 70-89 (Review) | ❌ <70 (Block)
| 分类 | 分值 | 核心规则 |
|---|---|---|
| 契约与分发 | 20 | 明确的动作列表;使用 |
| 输入验证 | 20 | 验证必填键;安全强制转换类型;空值防护 |
| 安全性 | 20 | 使用 |
| 错误处理 | 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
- on callable classes
without sharing - Silent failures (empty catch, swallowed exceptions)
- Inconsistent response shapes across actions
若将引入以下任何情况,请暂停并询问用户:
- 基于用户输入的动态方法执行(禁止反射)
- 循环内的SOQL/DML操作
- Callable类使用
without sharing - 静默失败(空catch块、吞掉异常)
- 动作间响应结构不一致
Common Anti-Patterns
常见反模式
- contains business logic instead of delegating
call() - 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
跨技能集成
| Skill | When to Use | Example |
|---|---|---|
| sf-apex | General Apex work beyond callable implementations | "Create trigger for Account" |
| sf-metadata | Verify object/field availability before coding | "Describe Product2" |
| sf-deploy | Validate/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中避免长时间运行的任务;必要时使用异步处理