in apex ~ read.

A more expressive interface for Post Install Scripts

I wanted to write about some patterns we're using to create post install scripts. I'm not wanting to go into detail on patterns, but rather to share some interesting ideas to make Salesforce installers easier to write and maintain.

What is a post install script?

A Salesforce post install script is a class contained in a package, which is run when the package is installed or upgraded. The class must be declared as global (which imposes some restrictions on change) and must implement the InstallHandler interface.

InstallHandler

The context argument gives us information of the previous version of the package already installed in the org.

This seems like a good way to create an installer, but it can quickly spiral out of control, like we'll show below.

A basic installer

Let's imagine that we have created a new Salesforce package (version 1.0), and we need to set up some actions on a fresh install. Easy! Let's create an installer:

A basic installer, v1.0

In this and all following examples we're omitting the methods that do the actual install/upgrade actions as they depend on the work that the package does. We'll just call them InitializePackage() or something similar, and obviate them.

Now let's imagine that we release version 1.1. We need to be careful not to run the same initialization actions again when the users upgrade from 1.0 to 1.1. So we have to modify the installer this way:

A basic installer, v1.1

In version 1.2, we introduce a new feature that changes the data format. We need to migrate existing data for versions earlier than 1.2:

A basic installer, v1.2

Now let's assume that in 1.3 we introduce another feature that will always need setting up some defaults, and needs to upgrade existing data for previous versions:

A basic installer, v1.3

Every time we make a new release, we need to go out and modify previous versions of the if statement. We can see how this can easily spiral out of control.

The basic solution has 2 main drawbacks:

  • Salesforce limits us to one and only one post install script class per package, which leads to one single installer having many responsibilities.

  • The InstallHandler interface is not very expressive, and we end up with a lot of boilerplate code to detect new installs, upgrades from old versions or upgrades to new versions
    .

One installer to rule them all

The first issue is quite trivial to work around. We can define a single installer class whose only responsibility is to call other installers for each and every feature. Every time a new feature requires an install or upgrade action, we can create a new installer class, which is called by they main installer:

A smarter installer

Feature10Installer, Feature12Installer and Feature13Installer are public (no need for global) classes that implement InstallHandler. Each of them has a single responsibility and can be individually tested. However, there is still a lot of boilerplate code (checking for previous versions of packages).

Templating the installer classes to avoid boilerplate code

In most cases, our installer will take different actions depending on the following cases:

  1. Is it a new install?
  2. Is it an upgrade from an older version which did not contain this feature?
  3. Is it an upgrade from a newer version that already contained this feature?

Normally in case 1 we can set up some defaults and in case 2 we can migrate old data. We don't tend to do much on case 3. We've used the template pattern to make this easier:

An installer template class

With this template pattern, all the common actions of detecting whether it's a fresh install or an upgrade are all done in the base class. The actual install actions are all performed in the subclasses.

To use it, you must override the abstract methods GetLastPackageVersionWithoutFeature (), which indicates which is the last package version to be considered "old".

Then, you may extend the 3 main actions, OnFreshInstall (), OnUpgradeFromOldPackage () and OnUpgradeFromRecentPackage () to perform your specific install actions.

Our example installers would look like this:

Feature10Installer

Feature12Installer

Feature13Installer

Now we a more expressive interface and have moved all the boilerplate code to the base class.