After spending quite a bit of time developing and supporting managed packages on Salesforce I’ve come across all sorts of creative, brilliant, and sometimes downright weird customizations of these. That’s the blessing of salesforce: everything is extensible, but when you’re trying to extend a package there’s a few unique things to watch out for. In light of that I’m going to try and start writing up some best practices around customizing managed packages. Starting off with everybody’s quick and easy customization:
<h3>Validation Rules</h3>
<div>Let’s look at FinancialForce PSA because it’s a great example of a complex app that often gets customized and extended, but the same fundamentals are true for plenty of packages from all sorts of vendors. For those not familiar with it there’s an object called “Assignment” that in essence says that a resource is assigned to a project. It’s incredibly common to see customers make a validation that says the resource must have it’s “Is Active” checkbox set to true. </div><div>
</div><div>What could possibly be wrong with that?!</div><div>
</div><div>Well let’s play the scenario through the whole lifecycle of a project. You assign your resource, they do some work, submit some timecards.</div><div>
</div><div>Now, they leave the company, and their resource is deactivated. </div><div>
</div><div>Time passes, and as the project wraps up management decides to credit the customer for some of the resource’s time. So they try and make one timecard set to non-billable. Which works, except it queues an @future method to roll up the “billable amount” on the assignment, which fails.</div><div>
</div><div>Now your assignment record is out of sync with your actual data! And if you don’t check your apex jobs listing you probably won’t have any idea either.</div><div>
</div><div>So how do you mitigate issues like this?</div><div>
</div><div>Make heavy use of the ISNEW()
and ISCHANGED(field)
functions in workflows or validations. If our workflow was changed from NOT(pse__Resource__r.pse__Is_Active__c)" to "(ISNEW() || (ISCHANGED(pse__Resource__c)) && NOT(pse__Resource__r.pse__Is_Active__c))
then we no longer have this problem. The validation rule no longer prevents unrelated updates to the record, and only adds errors if you’re actually making a “bad” change yourself. </div><div>
</div><div>Validation rules without this type of specificity on criteria are almost a form of collective punishment: it’s very easy to accidentally make old records invalid without realizing it, and you often end up punishing users and processes for bad data that wasn’t their doing.</div><div>
</div><div>Even outside of managed packages this is a best practice to avoid confusing your users (“But I didn’t change anything about the resource! This error doesn’t make sense!”), but it becomes vital in environments where there are processes beyond your control trying to update information.</div>