📜 ⬆️ ⬇️

Flexible Rest Services Architecture for Salesforce.com Development

Salesforce.com is a popular CRM system abroad. In Russia and the CIS countries there are not so many disrupters specializing in this platform, but vacancies constantly appear on the labor market and people are slowly but surely moving onto this platform.

There are not so many resources on Runet devoted to the development on the Force.com platform, I would like to write a number of articles related to development in order to increase the popularity of the platform.

The first article concerns a flexible architecture for writing Rest services.

Problem


The Force.com platform is designed to use the MVC pattern for developing applications and customizing existing functionality. The advantages of server rendering are indisputable, but it is often necessary to add more dynamism to the application, and in this case Rest services are indispensable.
')
In Salesforce "out of the box" is:

Also, the platform is known for its limits . And in this case, it is the Usage Limit API . To get rid of problems and not to touch the limits, the following solution was developed.

Decision


The solution is to use a regular VisualForce page with the controller to get the result of the query.

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> 


When the page loads, the execute method from the GS_RestApiController controller will be executed. And the result will be in 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 parameter to execute the desired command, and also store the correspondence between the command and its alias, if necessary.

All commands are stored in a 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, you simply need to extend the base class GS_Command and implement the perform () method, where the execution logic will be present.

To create an instance of the GS_Command class, a factory is intended - GS_CommandFactory .

 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 parameter passed, or creates an instance of the default class if such a command is not found.

The usage example is quite simple:
 var result = $.post('{!Page.RestApi}', {command : 'test'}); 

Result:
 {"result":"FirstTestResult + Params : {}","message":"FirstTestMessage","code":200} 

If requested without parameters, the default command is executed. The command name must match the COMMAND_NAME_TEMPLATE pattern described in GS_CommandFactory; you can also add an alias and command name match to the commandAliasNameMap of the GS_RestApiController class.

In my opinion, the architecture is convenient and easily expandable.

The source code for the project can be found on github .

PS I would like to receive feedback from readers on whether to continue to write articles about the development on this platform.

Thank.

Source: https://habr.com/ru/post/253641/


All Articles