mcpField
S
sf-industry-commoncore-callable-apex

sf-industry-commoncore-callable-apex

github.com/jaganpro开发424950 次安装26 周前更新
代码仓库

关于 sf-industry-commoncore-callable-apex

面向 Salesforce Industries Common Core callable Apex 的专项 Skill,可用于生成、审查和评分。重点是安全、明确的操作契约,以及与 OmniStudio 扩展点的兼容性。

主要功能

  1. 01生成 System.Callable 类
  2. 02审查 callable 实现
  3. 03按 120 分标准评分
  4. 04适配 OmniStudio 扩展点
  5. 05指导 Open Interface 迁移

适用场景

  1. 01为 OmniScript 入口创建 callable
  2. 02检查 CRUD/FLS 和操作分发
  3. 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.md
README.md
examples/Test_QuoteByProductCallable/
Industries_QuoteByProductCallable.cls
Industries_QuoteByProductCallableTest.cls
IndustriesCallableException.cls
TRANSCRIPT.md
examples/Test_VlocityOpenInterface2Conversion/
IndustriesCallableException.cls
MyCustomCallable.cls
MyCustomCallableTest.cls
MyCustomRemoteClass.cls
TRANSCRIPT.md
examples/Test_VlocityOpenInterfaceConversion/
IndustriesCallableException.cls
MyCustomCallable.cls
MyCustomCallableTest.cls
MyCustomVlocityOpenInterface2.cls
TRANSCRIPT.md

详细介绍

sf-industry-commoncore-callable-apex:Salesforce Industries Common Core 的可调用 Apex

Salesforce Industries Common Core 可调用 Apex 实现的专业工具。生成安全、确定且可配置的 Apex,并与 OmniStudio 和 Industries 扩展点无缝集成。

核心职责

  1. 可调用类生成:构建具有安全操作分派机制的 System.Callable 类
  2. 可调用类审查:审查现有可调用实现的正确性和风险
  3. 验证与评分:依据 120 分制评分标准进行评估
  4. Industries 适配:确保兼容 OmniStudio/Industries 扩展点

工作流(4 阶段模式)

阶段 1:需求收集

询问:

  • 入口点(OmniScript、Integration Procedure、DataRaptor 或其他 Industries 挂钩)
  • 操作名称(传入 call 的字符串)
  • 输入/输出契约(必需键、类型和响应结构)
  • 数据访问需求(对象/字段、CRUD/FLS 规则)
  • 副作用(DML、调用、异步要求)

然后:

  1. 扫描现有可调用类:Glob: **/*Callable*.cls
  2. 确定 Industries 扩展使用的共享工具类或基类
  3. 创建任务列表

阶段 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。

实现规则:

  1. 保持 call() 简洁;委托给私有方法或服务类
  2. 尽早验证并转换输入类型(安全处理 null)
  3. 强制执行 CRUD/FLS 和共享规则(with sharing、Security.stripInaccessible())
  4. 当参数包含记录集合时,进行批量化处理
  5. 在适用时,对 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
安全性20with 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 标准、测试模式和防护措施:


附带示例

备注

  • 优先采用确定性且考虑副作用的可调用操作
  • 保持操作契约稳定;对于破坏性变更,请新增操作
  • 避免在同步可调用项中执行长时间运行的工作;必要时使用异步方式

评论

支持 Markdown