关于 sf-industry-commoncore-callable-apex
面向 Salesforce Industries Common Core callable Apex 的专项 Skill,可用于生成、审查和评分。重点是安全、明确的操作契约,以及与 OmniStudio 扩展点的兼容性。
主要功能
- 01生成 System.Callable 类
- 02审查 callable 实现
- 03按 120 分标准评分
- 04适配 OmniStudio 扩展点
- 05指导 Open Interface 迁移
适用场景
- 01为 OmniScript 入口创建 callable
- 02检查 CRUD/FLS 和操作分发
- 03将 VlocityOpenInterface 迁移至 System.Callable
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jaganpro/ sf-skills --skill sf-industry-commoncore-callable-apex安装后在 Claude Code 中输入/sf-industry-commoncore-callable-apex确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CREDITS.mdREADME.mdexamples/Test_QuoteByProductCallable/Industries_QuoteByProductCallable.clsIndustries_QuoteByProductCallableTest.clsIndustriesCallableException.clsTRANSCRIPT.mdexamples/Test_VlocityOpenInterface2Conversion/IndustriesCallableException.clsMyCustomCallable.clsMyCustomCallableTest.clsMyCustomRemoteClass.clsTRANSCRIPT.mdexamples/Test_VlocityOpenInterfaceConversion/IndustriesCallableException.clsMyCustomCallable.clsMyCustomCallableTest.clsMyCustomVlocityOpenInterface2.clsTRANSCRIPT.md详细介绍
sf-industry-commoncore-callable-apex:Salesforce Industries Common Core 的可调用 Apex
Salesforce Industries Common Core 可调用 Apex 实现的专业工具。生成安全、确定且可配置的 Apex,并与 OmniStudio 和 Industries 扩展点无缝集成。
核心职责
- 可调用类生成:构建具有安全操作分派机制的
System.Callable类 - 可调用类审查:审查现有可调用实现的正确性和风险
- 验证与评分:依据 120 分制评分标准进行评估
- Industries 适配:确保兼容 OmniStudio/Industries 扩展点
工作流(4 阶段模式)
阶段 1:需求收集
询问:
- 入口点(OmniScript、Integration Procedure、DataRaptor 或其他 Industries 挂钩)
- 操作名称(传入
call的字符串) - 输入/输出契约(必需键、类型和响应结构)
- 数据访问需求(对象/字段、CRUD/FLS 规则)
- 副作用(DML、调用、异步要求)
然后:
- 扫描现有可调用类:
Glob: **/*Callable*.cls - 确定 Industries 扩展使用的共享工具类或基类
- 创建任务列表
阶段 2:设计与契约定义
定义可调用契约:
- 操作列表(明确的、带版本的字符串)
- 输入架构(必需键和类型)
- 输出架构(一致的响应封装)
推荐的响应封装:
{
"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视为组合输入架构 - 定义每个操作需要写入
outputMap的键(成功和错误情况均需定义) - 保留
methodName字符串,使其与 Callable 的action字符串一致 - 记录每个操作中的
options是必需、可选还是未使用
阶段 3:实现模式
原生 System.Callable(扁平参数,不耦合 Open Interface):
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 };
}
}
当调用方传入扁平参数且不需要集成 VlocityOpenInterface 时,使用原生模式。
Callable 骨架(与 VlocityOpenInterface 使用相同的输入):
集成 Open Interface 或调用方传入该结构时,请在 args 中使用 inputMap 和 options 键:
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 };
}
}
输入格式:调用方以 { 'inputMap' => Map<String, Object>, 'options' => Map<String, Object> } 的形式传入 args。为了向后兼容使用扁平参数的调用方,如果 args 不包含 'inputMap',则将 args 本身视为 inputMap,并使用空映射作为 options。
实现规则:
- 保持
call()简洁;委托给私有方法或服务类 - 尽早验证并转换输入类型(安全处理 null)
- 强制执行 CRUD/FLS 和共享规则(
with sharing、Security.stripInaccessible()) - 当参数包含记录集合时,进行批量化处理
- 在适用时,对 SOQL 使用
WITH USER_MODE
VlocityOpenInterface / VlocityOpenInterface2 实现:
实现 omnistudio.VlocityOpenInterface 或 omnistudio.VlocityOpenInterface2 时,使用以下签名:
global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
Map<String, Object> outputMap, Map<String, Object> options)
Open Interface 骨架:
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 实现规则:
- 通过
putAll()或单独调用put()将结果写入outputMap;不要从invokeMethod返回封装对象 - 成功时返回
true,遇到不支持的操作或失败时返回false - 使用与 Callable 相同的内部私有方法(参数为相同的
inputMap和options);只有入口点不同 - 使用相同的封装结构(
success、data、errors)填充outputMap,以保持一致
Callable 和 Open Interface 接受相同的输入(inputMap、options),并委托给具有相同私有方法签名的实现,以共享逻辑。
阶段 4:测试与验证
最低测试要求:
- 正向:支持的操作成功执行
- 负向:不支持的操作会抛出预期异常
- 契约:缺失或无效的输入会返回错误封装
- 批量:处理列表输入时不会触及限制
测试类示例:
@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'));
}
}
}
迁移:从 VlocityOpenInterface 迁移到 System.Callable
升级 Industries 扩展时,将 VlocityOpenInterface 或
VlocityOpenInterface2 的实现迁移到 System.Callable,并保持
操作契约稳定。请以 Salesforce 指南为准。
Salesforce Help
指南:
- 在
call()中将操作名称(methodName)保留为action字符串 - 将
inputMap和options作为键传入args:{ 'inputMap' => inputMap, 'options' => options } - 返回一致的响应封装,而不是修改
outMap - 保持
call()简洁;委托给使用(inputMap, options)签名的相同内部方法 - 为每个操作以及不支持的操作添加测试
迁移示例(模式):
// 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);
}
}
}
}
最佳实践(120 分评分)
| 类别 | 分数 | 关键规则 |
|---|---|---|
| 契约与分派 | 20 | 明确的操作列表;switch on;带版本的操作字符串 |
| 输入验证 | 20 | 验证必需键;安全转换类型;防护 null |
| 安全性 | 20 | with sharing;CRUD/FLS 检查;Security.stripInaccessible() |
| 错误处理 | 15 | 类型化异常;一致的错误封装;不使用空 catch |
| 批量化与限制 | 20 | 循环中不执行 SOQL/DML;支持列表输入 |
| 测试 | 15 | 正向/负向/契约/批量测试 |
| 文档 | 10 | 为类和操作方法编写 ApexDoc |
阈值:✅ 90+(就绪) | ⚠️ 70-89(审核) | ❌ <70(阻止)
⛔ 防护措施(强制要求)
如果会引入以下任何一项,请停止并询问用户:
- 根据用户输入动态执行方法(不使用反射)
- 在循环中执行 SOQL/DML
- 在可调用类上使用
without sharing - 静默失败(空的 catch 块、吞掉异常)
- 不同操作的响应结构不一致
常见反模式
call()包含业务逻辑,而不是委派处理- 操作名称没有版本号或未记录
- 未检查就假定输入映射包含所需键
- 响应类型混杂(有时是 Map,有时是 String)
- 未针对不支持的操作编写测试
跨技能集成
| 技能 | 使用时机 | 示例 |
|---|---|---|
| sf-apex | 可调用实现以外的一般 Apex 工作 | "为 Account 创建触发器" |
| sf-metadata | 编写代码前验证对象/字段是否可用 | "描述 Product2" |
| sf-deploy | 验证/部署可调用类 | "部署到沙盒" |
参考技能
请遵循以下文档中的核心 Apex 标准、测试模式和防护措施:
附带示例
- examples/Test_QuoteByProductCallable/ — 使用
WITH USER_MODE的只读查询示例 - examples/Test_VlocityOpenInterfaceConversion/ — 从旧版
VlocityOpenInterface迁移 - examples/Test_VlocityOpenInterface2Conversion/ — 从
VlocityOpenInterface2迁移
备注
- 优先采用确定性且考虑副作用的可调用操作
- 保持操作契约稳定;对于破坏性变更,请新增操作
- 避免在同步可调用项中执行长时间运行的工作;必要时使用异步方式