Flexible architecture of Rest services for development on the Salesforce.com platform

Salesforce.com is a popular CRM system abroad. In Russia and the CIS countries there are not many developers specializing in this platform, but job opportunities are constantly appearing in the labor market and people are slowly, but surely, moving to this platform.

In RuNet there are not many resources devoted to 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 deals with a flexible architecture for writing Rest services.

Problem


The Force.com platform is designed to use the MVC pattern to develop applications and customize existing functionality. The advantages of server rendering are undeniable, but often you need to add more dynamism to the application, and in this case, Rest services cannot be dispensed with.

Out of the box, Salesforce has:

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

Decision


The solution is to use the usual VisualForce page with the controller to get the query result.

As a result, we have a page with the following contents:



When loading the page will be executed method execute from controller GS_RestApiController . And the result will be bound in outputText .

Controller Code:

public class GS_RestApiController {
    private static final String COMMAND_NAME_PARAM = 'command';
    private Map commandAliasNameMap =  new Map{
        '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 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 params = new Map();
        public void setParams(Map 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 just need to extend the base class GS_Command and implement the perform () method , where execution logic will be present.

To create an instance of the GS_Command class, the 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 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.

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

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

When querying without parameters, the default command is executed. Team name must match the pattern COMMAND_NAME_TEMPLATE , described in GS_CommandFactory, you can also add a compatible alias and the name of the team in commandAliasNameMap class GS_RestApiController .

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

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

PS I would like to receive feedback from readers whether it is worth continuing to write articles about development on this platform.

Thanks.

Also popular now: