在 Salesforce 生态里,那个被普遍称为平台内核脚本的家伙,名叫 Apex。它是 Salesforce 自主设计并持续演进的一门语言:强类型、面向对象,语法接近 Java,却运行在 Salesforce 多租户云端执行环境,由平台统一编译、部署与执行。Apex 的角色并不是通用计算语言,而是围绕 CRM 数据与事务控制的业务逻辑语言,用来在平台上执行流程控制、数据持久化、权限校验、集成调用等任务。官方开发者指南把这种定位说得很清楚:Apex 允许开发者在 Salesforce 服务器上执行流程与事务控制语句,并与 API 调用配合,常被用于自动化业务过程与扩展平台行为。(Salesforce Developers, Salesforce Documentation)
Apex 的运行范式与传统 IaaS 或本地运行完全不同。代码被托管在 Salesforce 服务器,执行与数据库、对象模型、权限体系深度耦合,整个生命周期由平台治理,并受一组所谓的 Governor Limits 约束,例如单个事务内 SOQL 语句的数量、CPU 时间、堆内存、并发长事务配额等,这些限制的本质目的是保护多租户环境中的公平性与稳定性。(Salesforce Developers)
如果你熟悉 Java 或 C#,看 Apex 会有很强的亲切感:有类、有接口、有泛型风格的集合类型,也有异常处理和注解式编程模型。与 SQL 打交道时,Apex 使用两套查询语言:SOQL 与 SOSL。SOQL 的语义与 SQL 的 SELECT
接近,但针对 Salesforce 对象模型定制;SOSL 则面向全文检索与跨对象搜索。官方文档展示了语法、子句与性能注意事项,适合把它当作 CRM 模型的查询 DSL 来理解。(Salesforce Developers)
当你希望在记录被插入、更新、删除或恢复时自动执行业务规则,就需要 Apex Trigger。触发器可以定义 before
与 after
两类时机,用于校验、衍生字段、级联更新、写审计日志、调用外部系统等。Salesforce 还定义了详细的执行顺序,从系统校验、流程自动化,到触发器与提交阶段的关系,都有严格规则。(Salesforce Developers)
在较早的 Visualforce 技术栈里,Apex 充当控制器或控制器扩展,负责把页面事件与数据访问粘合起来;对于现代的 Lightning Web Components,Apex 通过 @AuraEnabled
注解暴露为可调用的服务方法,既支持通过 @wire
的响应式数据绑定,也支持命令式调用与缓存控制。这让前端组件能够以安全、受治理的方式访问平台数据与业务能力。(Salesforce Developers)
当逻辑会跨越大量记录、涉及外部 HTTP 调用、或需要延时执行时,Apex 提供一整套异步模型:Queueable
排队作业支持链式调度与复杂参数;Batch Apex
让你以批次方式处理超大数据集;Schedulable
支持基于 CRON 的定时;此外还可以使用可调用的异步方式把耗时任务移出同步事务。官方指南对这些模型的选择与约束有系统说明。(Salesforce Developers, Salesforce Ben)
Apex 能直接暴露为 REST 资源或 SOAP Web Service,通过注解声明端点、方法与 HTTP 动作,快速把平台能力安全地开放给外围系统。相较在中间件里再二次封装,这条路径能复用平台的权限、审计与事务能力。(Salesforce Developers)
Salesforce 强制要求变更需要测试覆盖,Apex 自带测试框架与隔离机制,支持注解式的测试类与测试方法、Mock 外部调用、以及事务回滚保证幂等。对多租户平台而言,这种内建测试是保护性工程实践的关键一环。(Salesforce Developers)
当标准配置、Flow 自动化或声明式工具足以覆盖需求时,不必为了写代码而写代码。Apex 的场景应聚焦在声明式方式无法覆盖、或需要高复杂度业务编排与可控事务边界的地方。触发器最佳实践也提倡把复杂逻辑下沉到可复用的类与服务中,通过单一触发器加处理器的模式来保持可维护性与可测试性。(Salesforce Developers)
下面给出一个小而完整的演示,覆盖触发器、触发器处理器、异步队列、批处理、定时作业、REST 端点与单元测试。示例业务设定为:当 Opportunity 成交时,自动创建一个后续 Task,并异步调用外部评分服务把客户打分回写;同时提供一个 REST 端点给外部系统查询评分;每日还有一个批处理清理过期评分记录。所有字符串均使用单引号,避免英文双引号,以满足格式限制。
触发器:监听 Opportunity 更新
trigger OpportunityTrigger on Opportunity (after update) {
if (Trigger.isAfter && Trigger.isUpdate) {
OpportunityTriggerHandler.handleAfterUpdate(Trigger.new, Trigger.oldMap);
}
}
触发器处理器:最小化 DML 与 SOQL,遵循批量化
public class OpportunityTriggerHandler {
public static void handleAfterUpdate(List<Opportunity> news, Map<Id, Opportunity> oldMap) {
List<Task> tasks = new List<Task>();
List<Id> toScore = new List<Id>();
for (Opportunity o : news) {
Opportunity oldO = oldMap.get(o.Id);
Boolean turnedWon = (o.StageName == 'Closed Won') && (oldO.StageName != 'Closed Won');
if (turnedWon) {
tasks.add(new Task(
WhatId = o.Id,
Subject = 'Post-Sale Follow-up',
Status = 'Not Started',
Priority = 'Normal'
));
toScore.add(o.AccountId);
}
}
if (!tasks.isEmpty()) {
insert tasks;
}
if (!toScore.isEmpty()) {
System.enqueueJob(new AccountScoringJob(toScore));
}
}
}
Queueable 作业:异步调用外部评分服务并回写 Account
public class AccountScoringJob implements Queueable, Database.AllowsCallouts {
private List<Id> accountIds;
public AccountScoringJob(List<Id> ids) { this.accountIds = ids; }
public void execute(QueueableContext ctx) {
List<Account> accts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
for (Account a : accts) {
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint('callout:ScoringService/scores?accountId=' + a.Id);
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() == 200) {
// 假设响应是簡單的分值,例如 87
Integer score = Integer.valueOf(res.getBody());
a.put('Customer_Score__c', score);
}
}
if (!accts.isEmpty()) {
update accts;
}
}
}
批处理:每天清理 90 天未更新的评分
global class ScoreCleanupBatch implements Database.Batchable<SObject>, Schedulable {
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id, Customer_Score__c, LastModifiedDate FROM Account ' +
'WHERE Customer_Score__c != NULL AND LastModifiedDate < LAST_N_DAYS:90'
);
}
global void execute(Database.BatchableContext bc, List<SObject> scope) {
List<Account> accs = (List<Account>)scope;
for (Account a : accs) { a.put('Customer_Score__c', null); }
if (!accs.isEmpty()) { update accs; }
}
global void finish(Database.BatchableContext bc) {}
global void execute(SchedulableContext sc) {
Database.executeBatch(new ScoreCleanupBatch(), 200);
}
}
定时调度示例(可在 Execute Anonymous 里运行)
// 每天凌晨 2:15 运行
System.schedule('Daily Score Cleanup', '0 15 2 * * ?', new ScoreCleanupBatch());
REST 端点:供外部系统查询评分
@RestResource(urlMapping='/score/*')
global with sharing class ScoreApi {
@HttpGet
global static String getScore() {
Id accountId = RestContext.request.params.get('accountId');
Account a = [SELECT Id, Customer_Score__c FROM Account WHERE Id = :accountId LIMIT 1];
return String.valueOf(a.get('Customer_Score__c'));
}
}
测试代码:覆盖触发器、异步与 REST
@IsTest
private class DemoTests {
@IsTest
static void testClosedWonFlow() {
// 构造账户与商机
Account acc = new Account(Name='Acme');
insert acc;
Opportunity o = new Opportunity(Name='Deal', StageName='Prospecting', CloseDate=Date.today().addDays(10), AccountId=acc.Id);
insert o;
// 模拟外部评分服务
Test.setMock(HttpCalloutMock.class, new SimpleScoreMock(88));
Test.startTest();
o.StageName = 'Closed Won';
update o; // 触发器将插入 Task 并排队评分作业
Test.stopTest(); // 等待异步完成
// 验证评分已写回
acc = [SELECT Customer_Score__c FROM Account WHERE Id = :acc.Id];
System.assertEquals(88, acc.get('Customer_Score__c'));
}
// 简单的 HTTP 模拟
private class SimpleScoreMock implements HttpCalloutMock {
Integer score;
SimpleScoreMock(Integer s) { score = s; }
public HTTPResponse respond(HTTPRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setBody(String.valueOf(score));
return res;
}
}
@IsTest
static void testRestApi() {
Account a = new Account(Name='Beta');
a.put('Customer_Score__c', 73);
insert a;
RestRequest req = new RestRequest();
req.requestUri = '/services/apexrest/score?accountId=' + a.Id;
req.httpMethod = 'GET';
RestContext.request = req;
RestContext.response = new RestResponse();
String body = ScoreApi.getScore();
System.assertEquals('73', body);
}
}
这组代码体现了 Apex 的典型工程化实践:在触发器里只做路由,不直接写复杂逻辑;批量化处理集合;外部调用放入 Queueable 异步,避免阻塞事务;通过批处理与定时作业维护数据健康;用内建测试框架隔离与校验。异步与队列的选择、限制与监控方法,可以在官方文档里查到细节,包括如何链式队列、何时允许 Callout、以及不同模型的适用场景。(Salesforce Developers)
面向数据的 DSL
所有与数据交互的读写,都建议用 SOQL、SOSL 与 DML 语句完成。SOQL 的 SELECT ... FROM ... WHERE ...
思路与 SQL 相近,语法却与对象模型绑定,包含关系查询、聚合、子查询、选择性过滤等;SOSL 用于跨对象的全文检索。理解它们与对象权限、字段级权限的协作,是写出安全代码的关键。(Salesforce Developers)
多租户下的资源配额
Governor Limits 是平台秩序的守门人。它强制你在设计时考虑批量化、缓存、异步解耦与最少化 SOQL/DML 的模式。例如触发器最佳实践页面强调,用集合合并 DML 操作、避免循环里查询与写入、把重活交给异步。监控与调优这部分,建议在沙盒里用日志与限制检查辅助验证。(Salesforce Developers)
从工程角度看,Apex 既是语言,也是运行时契约。语言层面提供熟悉的面向对象与注解模型;运行时层面与 Salesforce 的对象、权限、审计、自动化、集成栈紧密耦合。正因为这种耦合,Apex 才适合落地那些需要强事务一致性、需要尊重平台共享资源约束、以及需要借力平台安全边界的场景。它不是要取代 Java、Python 那类通用语言,而是把 CRM 核心域的复杂性收束在一个可治理的环境里。对于前端的 LWC,Apex 成为可被 @AuraEnabled
暴露的服务端 API;对于集成,则能以注解快速暴露 REST 或 SOAP 服务;对于批处理与延迟任务,平台的异步能力提供了低维护成本的解法。(Salesforce Developers)
想进一步系统化学习,建议从以下官方入口出发:Apex 开发者指南的总目录、Apex 参考手册、以及 SOQL 与 SOSL 指南。这些文档持续更新,覆盖语言特性、API、限制、优化实践、以及版本演进。(Salesforce Developers)
一句话回看主题
Salesforce 自研的 Apex 语言,生来就为平台内的业务自动化、数据访问、事务控制与外部集成而生,通过触发器、控制器、异步作业与 Web 服务四大支点,构成了 CRM 应用扩展与定制的骨架;它依赖 SOQL/SOSL 操作数据,遵循 Governor Limits 的资源契约,并借助内建测试框架确保可演进的质量。这样一门语言,最大的价值不是让你写出更酷的语法,而是在多租户、强约束的云平台里,把复杂业务安全而稳定地跑起来。(Salesforce Developers)
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。