write bulletproof trigger code

34
new String[] {"hip”, "hip”}; (hip hip array!) Q: What’s the object-oriented way to become wealthy? A: Inheritance To understand what recursion is, you must rst understand recursion. Q: Why don't jokes work in octal? A: Because 7 10 11 Session: Write Bulletproof Trigger Code

Upload: salesforce-developers

Post on 22-Jan-2018

668 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Write bulletproof trigger code

new String[] {"hip”, "hip”};

(hip hip array!)

Q: What’s the object-oriented way to become wealthy?

A: Inheritance

To understand what recursion is, you must first understand recursion.

Q: Why don't jokes work in octal?

A: Because 7 10 11

Session: Write Bulletproof Trigger Code

Page 2: Write bulletproof trigger code

Write Bulletproof Trigger Code

 Steve Cox  Senior Software Developer  [email protected]  @SteveCox67  

Writing Apex triggers can be simple, safe, and fun!

Page 3: Write bulletproof trigger code

 Safe harbor statement under the Private Securities Litigation Reform Act of 1995:

 This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties materialize or if any of the assumptions proves incorrect, the results of salesforce.com, inc. could differ materially from the results expressed or implied by the forward-looking statements we make. All statements other than statements of historical fact could be deemed forward-looking, including any projections of product or service availability, subscriber growth, earnings, revenues, or other financial items and any statements regarding strategies or plans of management for future operations, statements of belief, any statements concerning new, planned, or upgraded services or technology developments and customer contracts or use of our services.

 The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new functionality for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our operating results and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of any litigation, risks associated with completed and any possible mergers and acquisitions, the immature market in which we operate, our relatively limited operating history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our service and successful customer deployment, our limited history reselling non-salesforce.com products, and utilization and selling to larger enterprise customers. Further information on potential factors that could affect the financial results of salesforce.com, inc. is included in our annual report on Form 10-K for the most recent fiscal year and in our quarterly report on Form 10-Q for the most recent fiscal quarter. These documents and others containing important disclosures are available on the SEC Filings section of the Investor Information section of our Web site.

 Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently available and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions based upon features that are currently available. Salesforce.com, inc. assumes no obligation and does not intend to update these forward-looking statements.

Safe Harbor

Page 4: Write bulletproof trigger code

Steve Cox Senior Software Developer, Exponent Partners

Page 5: Write bulletproof trigger code

1.  What are the “bullets”?

a)  Pop Quiz

b)  What makes trigger writing challenging?

2.  What’s our defense?!

a)  Don’t write a trigger

b)  Avoid DML

c)  Write great unit tests

d)  Use a good trigger framework

Agenda

Page 6: Write bulletproof trigger code

1.  // When an account changes, set the description on all contacts associated with that account2.   trigger MyAccountTrigger on Account(before insert, before update) {3.  Account a = Trigger.new[0];4.  Contact[] contacts = [SELECT Salutation, FirstName FROM Contact WHERE AccountId = :a.Id];

5.  for (Contact c : contacts) {6.  c.Description = a.Name + ': ' + c.Salutation + ' ' + c.FirstName;7.  update c;8.  }9.  }

How many “bullets” is this trigger code littered with? “Bullets” Quiz

Page 7: Write bulletproof trigger code

1.  A variable number of records may be passed to your trigger

2.  Your trigger may be called multiple times due to workflow, other triggers, process builder, or flows

3.  The order of execution of multiple triggers on the same object is not defined

4.  Performance may be an issue if the object contains many records

5.  Limits!

6.  Testing triggers can be very involved

7.  Writing great Apex code is difficult

Several factors make trigger writing challenging “Bullets”

Page 8: Write bulletproof trigger code

Dealing with “Bullets” Defense #1: Don’t Write a Trigger - Configure

Page 9: Write bulletproof trigger code

•  Formula fields

•  Validation rules

•  Workflow

•  Roll-up summary fields

•  Approval processes

•  Visual flow

•  Process builder…

When possible, use point and click solutions rather than custom code Don’t Write a Trigger - Configure

. . . Use ANYTHING BUT a trigger

Page 10: Write bulletproof trigger code

Dealing with “Bullets” Defense #2: Avoid DML

Page 11: Write bulletproof trigger code

•  Just because a trigger is called doesn’t mean there’s anything that needs to be done

•  Don’t update records that don’t need to be updated

•  Don’t execute code that doesn’t need to be executed

Avoiding DML protects us from several “bullets”

•  Limits

•  Multiple calls and trigger “loops”

•  Performance issues

P.S. Be careful relying on static variables to determine multiple executions

Avoid DML

Page 12: Write bulletproof trigger code

1.   public class AccountHandler extends BaseHandler {

2.  public override void bulkAfterUpdate(Map<Id,SObject> oldAccounts, Map<Id,SObject> accounts) {3.  Account[] accountsToUpdate = new Account[]{};4.  for (Account a : accounts) {5.  accountsToUpdate.add(new Account(Id = a.Id, Description = a.Name + ': ' + a.AccountNumber);6.  }

7.  update accountsToUpdate;8.  }9.  }

What changes could be made to the code below to postpone or isolate DML? Avoid DML – But How?

Page 13: Write bulletproof trigger code

1.  public class AccountHandler extends BaseHandler {

2.  public override void bulkAfterUpdate(Map<Id,SObject> oldAccounts, Map<Id,SObject> accounts) {3.  Account[] accountsToUpdate = new Account[]{};4.  for (Account a : accounts) {5.  Account oldAccount = (Account)oldAccounts.get(a.Id);6.  if ((a.Name != oldAccount.Name) || (a.AccountNumber != oldAccount.AccountNumber)) {7.  accountsToUpdate.add(new Account(Id = a.Id, Description = a.Name + ': ' + a.AccountNumber));8.  }9.  }

10.  update accountsToUpdate;11.  }12. }

Method 1: has a relevant field changed? Avoid DML – But How?

Advantages / Disadvantages?

Page 14: Write bulletproof trigger code

1.   public class AccountHandler extends BaseHandler {

2.  public override void bulkAfterUpdate(Map<Id,SObject> oldAccounts, Map<Id,SObject> accounts) {3.  Account[] accountsToUpdate = new Account[]{};4.  for (Account a : (Account[])accounts.values()) {5.  String newDescription = a.Name + ': ' + a.AccountNumber;6.  if (a.Description != newDescription) {7.  accountsToUpdate.add(new Account(Id = a.Id, Description = newDescription);8.  }9.  }

10.  update accountsToUpdate;11.  }12.  }

Method 2: are we making a change? Avoid DML – But How?

Advantages / Disadvantages?

Page 15: Write bulletproof trigger code

1.   public class AccountHandler extends BaseHandler {

2.  public override void bulkAfterUpdate(Map<Id,SObject> oldAccounts, Map<Id,SObject> accounts) {3.  Map<Id,SObject> changedAccounts = accounts.clone(true);4.  Account[] accountsToUpdate = new Account[]{};5.  for (Account a : changedAccounts) {6.  // logic / update a.field17.  // logic / update a.field28.  // ...9.  // logic / update a.fieldN10.  if (!accountsAreEqual(a, accounts.get(a.Id))) {11.  accountsToUpdate.add(a);12.  }13.  }

14.  update accountsToUpdate;15.  }16.  }

Method 3: are we making a change (complex logic)? Avoid DML – But How?

Advantages / Disadvantages?

Page 16: Write bulletproof trigger code

Dealing with “Bullets” Defense #3: Write Great Unit Tests

Page 17: Write bulletproof trigger code

1.  Design for testability!

2.  Use asserts

3.  Always test in bulk

4.  Test the happy path

5.  Don’t test if the trigger handler is disabled

6.  (?) Test error and exception conditions

7.  (?) Cover all logical branches

Write Great Unit Tests

Page 18: Write bulletproof trigger code

1.  trigger MyAccountTrigger on Account(before insert, before update) {2.  Account[] accountsToUpdate = new Account[]{};

3.  for (Account a : (Account[])Trigger.new) {4.  String newDescription = a.Name + ': ' + a.AccountNumber;5.  if (a.Description != newDescription) {6.  accountsToUpdate.add(new Account(Id = a.Id, Description = newDescription);7.  }8.  }

9.  update accountsToUpdate;10. }

Design: why is this code difficult to test? Write Great Unit Tests

Suggestions?

Page 19: Write bulletproof trigger code

1.  public class AccountHandler extends BaseHandler {2.  public override void bulkAfterUpdate(Map<Id,SObject> oldAccounts, Map<Id,SObject> accounts) {3.   update Accounts.setDescriptions((Account[])accounts.values());4.  }5. }

6.  public class Accounts extends Domain { // enterprise patterns7.  public static Account[] setDescriptions(Account[] accounts) { // reusable!8.  Utils.preCondition(null != accounts, 'a list of accounts is required');9.  Account[] accountsToUpdate = new Account[]{};10.  for (Account a : accounts) {11.  String newDescription = a.Name + ': ' + a.AccountNumber;12.  if (a.Description != newDescription) {13.  accountsToUpdate.add(new Account(Id = a.Id, Description = newDescription);14.  }15.  }16.  return accountsToUpdate; // no DML!17.  }

Design: fixes Write Great Unit Tests

Advantages / Disadvantages?

Page 20: Write bulletproof trigger code

1. @IsTest class TestAccounts extends BaseTest { // base does setup, creates myAdminUser, etc.2.  static testMethod void testSetDescriptions() {3.  System.runAs(myAdminUser) {4.  Account[] accounts = newAccounts('name'); // no DML!

5.  Test.startTest();6.  // no accounts7.  System.assert(Accounts.setDescriptions(new Account[]{}).isEmpty());

8.  // bulk accounts with changed descriptions9.  Account[] results = Accounts.setDescriptions(accounts);10.  Test.stopTest();

11.  for (Integer i = 0; i < accounts.size(); ++i) {12.  //TODO assert description was correctly set13.  }14.  }15.  . . .

Create a base class; always test in bulk Write Great Unit Tests

Advantages / Disadvantages?

Page 21: Write bulletproof trigger code

1.  @IsTest class TestAccounts extends BaseTest {2.  static testMethod void testSetDescriptions() {3.  System.runAs(myAdminUser) {4.  Test.startTest();5.  //TODO test with null values for Name, AccountNumber

6.  //TODO test with blank values for Name, AccountNumber

7.  //TODO test with long values for Name, AccountNumber

8.  //TODO test case where description doesn’t change

9.  //TODO test Accounts.setDescriptions(Accounts.setDescriptions())10.  Test.stopTest();11.  }12.  }13.  }

Cover all logic Write Great Unit Tests

Page 22: Write bulletproof trigger code

1.  @IsTest class TestAccounts extends BaseTest {2.  static testMethod void testTriggerHandlerAssertions() {3.  System.runAs(myAdminUser) {4.  Test.startTest();5.  try {6.  Accounts.setDescriptions(null);7.  System.assert(false, ‘records are required');8.  } catch (PreConditionException ex) {}9.  Test.stopTest();10.  }11.  }12.  }

Test error conditions & exceptions Write Great Unit Tests

Page 23: Write bulletproof trigger code

1. @IsTest class TestAccounts extends BaseTest {2.  static testMethod void testTriggerHandler() {3.  if (Utils.triggerIsEnabled('MyAccountTrigger', 'SetAccountDescriptions')) {4.  System.runAs(myAdminUser) {5.  Account[] accounts = newAccounts('name');6.  insert accounts;7.  //TODO assert descriptions are NOT set

8.  Test.startTest();9.  update accounts;10.  Test.stopTest();

11.  //TODO assert descriptions ARE set12.  }13.  . . .

Test the trigger Write Great Unit Tests

Page 24: Write bulletproof trigger code

Dealing with “Bullets” Defense #4: Use a Good Trigger Framework

Page 25: Write bulletproof trigger code

A framework can provide consistency and save loads of time

•  Allows you to specify the processing order

•  Makes enabling/disabling individual handlers a snap

•  Provides centralized diagnostics and error handling

•  Decreases development and ramp-up time

Use a Good Trigger Framework

Page 26: Write bulletproof trigger code

Framework Features Use a Good Trigger Framework

1.  trigger MyObj on MyObj__c(after delete, after insert, after undelete,2.  after update, before delete, before insert, before update) {3.  new TriggerFactory().handle(MyObj__c.SObjectType); // non-testable / non-reusable code4. }

// Trigger body is reduced to a single line

1.  public static Handler__c[] handlers {2.  get {3.  if (null == handlers) {4.  handlers = [SELECT ... FROM TriggerSettings WHERE Active__c = true ORDER BY Type__c, Order__c];5.  //categorize as desired – SObjectType, subsystem, feature, etc.6.  }7.  return handlers;8.  }

9.  private set;10. }

// Handlers are declared in custom settings, where they can be disabled or the order set

Page 27: Write bulletproof trigger code

Framework Features Use a Good Trigger Framework

1.  public void handle(SObjectType o) { // centralized trigger handler2.  final String type = String.valueOf(o);3.  if (!Boolean.valueOf(TriggerSettings.getInstance().EnableTriggers__c)) return;

4.  try {5.  if (!Boolean.valueOf(TriggerSettings.getInstance().get(‘Trigger_’ + type)) return;6.  } catch (Exception ex) {} // if no setting exists, handlers are enabled by default7.  8.  for (Handler__c h : handlers) {9.  if (h.Active__c && h.Type__c.equalsIgnoreCase(type)) {10.  final String handlerName = h.Name.trim();11.  try {12.  ITrigger t = (ITrigger)Type.forName(handlerName).newInstance();13.  if (null != t) {14.  execute(t);15.  }16.  } catch (NullPointerException ex) {}17.  }18.  }19. }

// Centralized dispatcher enforces disabling

Page 28: Write bulletproof trigger code

Framework Features Use a Good Trigger Framework

1.  private void execute(ITrigger handler) { // centralized trigger handler2.  try {3.  if (Trigger.isBefore) {4.  if (Trigger.isInsert) {5.  handler.bulkBeforeInsert(Trigger.newMap);6.  for (SObject so : Trigger.new) {7.  handler.beforeInsert(so);8.  }9.  } else if (Trigger.isUpdate) {10.  handler.bulkBeforeUpdate(Trigger.oldMap, Trigger.newMap);11.  for (SObject so : Trigger.old) {12.  handler.beforeUpdate(so, Trigger.newMap.get(so.Id));13.  }14.  } else if (Trigger.isDelete) {15.  handler.bulkBeforeDelete(Trigger.oldMap);16.  for (SObject so : Trigger.old) {17.  handler.beforeDelete(so);18.  }19.  }20.  } else if (Trigger.isAfter) {21.  . . .

// A common, logical ‘execute’ pattern provides consistent routing

Page 29: Write bulletproof trigger code

Framework Features Use a Good Trigger Framework

1.  public virtual class BaseHandler extends BaseClass implements ITrigger {2.  /** the records that will be inserted when "andFinally" is called */3.  protected SObject[] toInsert = new SObject[]{};4.  . . .5.  6.  /** handle bulk "before" processing of insert */7.  public virtual void bulkBeforeInsert(Map<Id,SObject> sos)8.  { Utils.preCondition(Test.isRunningTest() || (Trigger.isBefore && Trigger.isInsert)); }9.  10.  /** handle bulk "before" processing of update */11.  public virtual void bulkBeforeUpdate(Map<Id,SObject> oldSos, Map<Id,SObject> sos)12.  . . .

13.  /** insert, update, and delete the processed records */14.  public virtual void andFinally() {15.  Utils.preCondition(isValid());16.  17.  insert toInsert;18.  . . .

// Base classes make handler writing a snap

Page 30: Write bulletproof trigger code

Conclusion

Page 31: Write bulletproof trigger code

1.  Avoid them if possible!

2.  Only do DML when absolutely necessary

3.  Test thoroughly

4.  Use a good trigger framework

Writing bulletproof trigger code is challenging Summary

Page 32: Write bulletproof trigger code

Sample frameworks and additional guidance Additional Resources

•  Trigger Frameworks and Apex Trigger Best Practices – Kevin O’Hara

•  An architecture framework to handle triggers in the Force.com platform – Hari Krishnan

•  Trigger Pattern for Tidy, Streamlined, Bulkified Triggers – Tony Scott

•  Advanced Apex Programming – Dan Appleman

Page 33: Write bulletproof trigger code

Share Your Feedback, and Win a GoPro!

3 Earn a GoPro prize entry for each completed survey

Tap the bell to take a survey 2Enroll in a session 1

Page 34: Write bulletproof trigger code

Questions?