The Classic Salesforce platform is designed to use the MVC pattern for developing applications and customizing existing functionality. The benefits of server rendering are obvious, but we often need more dynamic in the application. In this case, Rest Services will help us.
By default, Salesforce has the following capabilities:
The platform is also known for its limits. In this case — API Usage Limit.
We will get rid of the problems and overcome the limits.
Solution
The solution is to use the standard Visualforce page with a custom controller to get the result of the request.
As a result, we have a page with the following contents:
<apex:page controller="GS_RestApiController" action="{!execute}" contenttype="text/javascript" showHeader="false" sidebar="false">
<apex:outputText value="{!response}" escape="false"/>
</apex:page>
The method execute from the GS_RestApiController controller will be executed when the page is loaded. The result will be binded into the outputText.
Controller Code:
public class GS_RestApiController {
private static final String COMMAND_NAME_PARAM = 'command';
private Map<String, String> commandAliasNameMap = new Map<String, String>{
'test' => 'FirstTest'
};
public String response {get; private set;}
public GS_RestApiController() { }
public void execute() {
this.response = getCommand().execute().toJSON();
}
private GS_CommandContainer.GS_Command getCommand() {
Map<String, String> params = ApexPages.currentPage().getParameters();
String commandName = params.get(COMMAND_NAME_PARAM);
if (commandAliasNameMap.containsKey(commandName)) {
commandName = commandAliasNameMap.get(commandName);
}
params.remove(COMMAND_NAME_PARAM);
return GS_CommandFactory.create(commandName, params);
}
}
In the controller, we use the command param to execute the command, and we have the map between the command and its alias if necessary.
All commands are stored in container class — GS_CommandContainer
public class GS_CommandContainer {
public abstract class GS_Command {
private Map<String, String> params = new Map<String, String>();
public void setParams(Map<String, String> params) {
this.params = params;
}
public GS_RestResponse execute() {
try {
Object resultObject = perform();
return new GS_RestResponse(GS_StatusCode.OK, getMessage(), resultObject);
} catch (GS_Exception exp) {
String message = exp.getMessage() + exp.getStackTraceString();
return new GS_RestResponse(GS_StatusCode.ERROR, message);
} catch (Exception exp) {
String message = exp.getMessage() + exp.getStackTraceString();
return new GS_RestResponse(GS_StatusCode.ERROR, message);
}
}
public abstract Object perform();
public virtual String getMessage() {
return null;
}
}
public class GS_DefaultCommand extends GS_Command {
public override Object perform() {
return 'This is defult result.';
}
public override String getMessage() {
return 'This is default message.';
}
}
Thus, to add a new command, it is necessary simply to extend the base class GS_Command and implement the perform() method where the execution logic will be present.
GS_CommandFactory is designed to create an instance of the GS_Command class.
public class GS_CommandFactory {
private static final String DOT = '.';
private static final String COMMAND_CONTAINER_NAME = 'GS_CommandContainer';
private static final String DEFAULT_COMMAND_NAME = 'GS_DefaultCommand';
private static final String COMMAND_NAME_TEMPLATE = 'GS_{0}Command';
private static final String COMMAND_NAME_TEMPLATE_WITH_CONTAINER = COMMAND_CONTAINER_NAME + DOT + COMMAND_NAME_TEMPLATE;
private static final String DEFAULT_COMMAND_NAME_TEMPLATE_WITH_CONTAINER = COMMAND_CONTAINER_NAME + DOT + DEFAULT_COMMAND_NAME;
public static GS_CommandContainer.GS_Command create() {
Type commandType = Type.forName(DEFAULT_COMMAND_NAME_TEMPLATE_WITH_CONTAINER);
GS_CommandContainer.GS_Command command = (GS_CommandContainer.GS_Command)commandType.newInstance();
return command;
}
public static GS_CommandContainer.GS_Command create(String commandName, Map<String, String> params) {
if(String.isBlank(commandName)) {
create();
}
String commandClassName = String.format(COMMAND_NAME_TEMPLATE_WITH_CONTAINER, new String[] {commandName});
Type commandType = Type.forName(commandClassName);
if(commandType == null) {
commandType = Type.forName(DEFAULT_COMMAND_NAME_TEMPLATE_WITH_CONTAINER);
}
GS_CommandContainer.GS_Command command = (GS_CommandContainer.GS_Command)commandType.newInstance();
command.setParams(params);
return command;
}
}
It creates an instance of the required command, depending on the given parameter, or creates an instance of the default class if no such command is found.
The example of use is quite simple:
var result = $.post('{!Page.RestApi}', {command : 'test'});
The result is:
{"result":"FirstTestResult + Params : {}","message":"FirstTestMessage","code":200}
When you perform a request without any parameters, the default command is executed. The command name must match the COMMAND_NAME_TEMPLATE template described in GS_CommandFactory, and it is also possible to add the alias and command name to the commandAliasNameMap in the GS_RestApiController.
From our side, the architecture is convenient and easily extensible.
The source code you can find on GitHub.
Nowadays, when Lightning Components is became standard for developing UI. It make less sense. But it can help you if you make custom UI with any JS framework (Angular, React, Vue) or you faced with any problem related with AuraEnabled methods.