Friday, June 23, 2017

Post Install Script Framework

As an ISV, many times we have to write post install scripts for upgrade. Salesforce provides facility to do that : https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_install_handler.htm

However are quite a few limitations with this:

  • It is hard to make sure if post install code is already executed or not
  • When developing upgrade script, we don't know what version it is going to be when it gets published
  • Hard to stop the execution or do the retry


Hence we tried to implement same approach using change set and integrated with Salesforce Post Install Handler. Changeset is industry wide practice used for a long time and here are some benefits:


  • Multiple Post Install Scripts
  • Execution of Post Install Scripts in Order - and only once
  • On Error in any Script
    • Stop/Halt the execution
    • Send Error Email
  • On Successful completion of all scripts
    • send summary email of all scripts
  • Each script gets full set of governance limit
    • In case of Salesforce, entire Batch is devoted to a given script


With above points in mind, we created below framework, where ISV can just plugin in any post install script with minimal effort:


Framework



1. Entry point class - which implements Salesforce interface InstallHandler
   This class just calls PostInstallService.startService
 
2. PostInstallService.startService
   This class scans for all the classes which extends PostInstallScriptTemplate in current namespace
   It inserts them into PostInstallScript__c object, if it doesn't exist already.

3. PostInstallService.startService Calls PostInstallService.executeNextScript
 
4. PostInstallService.executeNextScript
   Based on the data in PostInstallScript__c, it will call next PostInstallScriptTemplate(N)
   PostInstallScriptTemplate is batch interface so execution will be done asynchronous fashion
   [Note: There will be callback to PostInstallService, when PostInstallScriptTemplate(N) is completed/errored]
 
5. Once the PostInstallScriptTemplate(N) is completed/errored
   It will update the PostInstallScript__c object with Status and Execution Log
 
6. PostInstallScriptTemplate(N) will call back the framework PostInstallService.executeNextScript

7. PostInstallService.executeNextScript
   based on data in PostInstallScript__c in, it will either:
a) Halt execution (if error)
b) Move on to the next script
c) Move on to finish if all of post install scripts are successfully completed

8. A user interface to display currently pending/Completed/Errored Scripts along with Execution Logs

9. A user interface provides facility to resubmit if Errored
 

Post Install Scripts



 
ISV can write Post Install script in two ways:

1) If Batch context is not needed, we can write Post install script as below,
description, sequence number and execution log is stored in database.
Actual post install logic is in executeScript method


/**
 * Created by cshah on 5/30/2017.
 */

public with sharing class PostInstallScript1 extends PostInstallScriptTemplate {

    private String executionLog;

    public override void executeScript(Database.BatchableContext bc, List<SObject> sObjects) {
        System.debug('PostInstallScript1 : execute : hoping to get executed only once ');
        executionLog  = 'script 1 done. ';
    }

    public override Integer getSequenceNumber() {
        System.debug('PostInstallScript1 : getSequenceNumber ');
        return 1;
    }

    public override String getExecutionLog() {
        System.debug('PostInstallScript1 : getExecutionLog ');
        return executionLog;
    }

    public override String getDescription() {
        System.debug('PostInstallScript1 : getDescription ');
        return 'Script 1 Description ';
    }
}


2) If we need to query 50k+ records or update 10k+ records, we have to use Batch context and here is another way to write post install Script. In this case, we will need to override start and finish methods just like we do for any Salesforce Batch


/**
 * Created by cshah on 5/30/2017.
 */

public without sharing class PostInstallScript3 extends PostInstallScriptTemplate {

    private String executionLog;
    private Integer processedCount = 0;

    public override void executeScript(Database.BatchableContext bc, List<SObject> sObjects) {
        System.debug('PostInstallScript3 : execute : hoping to get executed only once ');
        executionLog  = 'script 3 done. ';
        processedCount += sObjects.size();
    }

    public override Integer getSequenceNumber() {
        System.debug('PostInstallScript3 : getSequenceNumber ');
        return 3;
    }

    public override String getExecutionLog() {
        System.debug('PostInstallScript3 : getExecutionLog ');
        executionLog = ' Processed ' + processedCount + ' records ';
        return executionLog;
    }

    public override String getDescription() {
        System.debug('PostInstallScript3 : getDescription ');
        return 'Script 3 Description ';
    }

    public override Integer getBatchSize() {
        System.debug('PostInstallScript3 : getBatchSize ');
        return 1;
    }

    public override Database.QueryLocator startScript(Database.BatchableContext bc) {
        System.debug('PostInstallScript3 : startScript ');
        return Database.getQueryLocator('select id, name from account limit 201');
    }

    public override void  finishScript(Database.BatchableContext bc) {
        System.debug('PostInstallScript3 : finishScript ');
    }

}



User Interface


User interface allows to view the post install script, and their execution log. 
It allows to resubmit in case of error

Source code 

1. It can be found at github : https://github.com/c-shah/PostInstallScriptFramework
2. Or as unmanaged package : https://login.salesforce.com/packaging/installPackage.apexp?p0=04tf400000099bW