Home » Blog » Apex Controllers: Providing Data ‘On-Demand’

Apex Controllers: Providing Data ‘On-Demand’

Ready for an opportunity to scrutinize a coding habit or two? Take a look at the snippet below. If you tend to write controllers that follow this format, read on.

The 'Standard' Controller Pattern

public with sharing class LoanEditController {
   public Loan__c loan { get; set; }
   public Boolean showBillingStatement { get; set; }
   public String officer { get; set; }

   public LoanEditController() {
      final Id loanId = Utils.getIdPageParam('id');
      loan = [SELECT Status__c, Officer__r.Name FROM Loan__c WHERE id = :loanId];
      showBillingStatement = ('Approved' == loan.Status__c);
      officer = loan.Officer__r.Name;
   }

   public void save() {
      if ((null == officer) || (0 == officer.trim().length())) {
         throw new ValidationException('Specify a loan officer');
      }

      loan.Officer__c = [SELECT Id FROM User WHERE Name = :officer].Id;
      insert loan;
   }
}

If you’re like us, you’ve spent plenty of time reading Salesforce documentation that presents code just like this. The implied standard pattern for creating a controller for a VisualForce page seems to be:

  • declare a property for each element that will be displayed on the page
  • create a constructor that initializes the properties
  • create methods to handle user-interactions with the page

Rather than being populated ‘on-demand’, the properties of a LoanEditController are all set when the constructor is executed. This makes the class difficult to reuse and unit test.

An Alternative Pattern

Here’s a second pass at the same controller, implemented a bit differently.

public with sharing class LoanController {
   private static final String SHOW_STATEMENT_STATUS = 'Approved';

   public Id loanId {
      get;
      set {
         if (value != loanId) {
            loanId = value;
            loan = null; // force a refresh
         }
      }
   }

   public Loan__c loan {
      get {
         if (null == loan) {
            if (null != loanId) {
               loan = [SELECT Status__c, Officer__r.Name FROM Loan__c WHERE id = :loanId];
               officer = loan.Officer__r.Name;
            } else {
               loan = new Loan__c();
            }
         }
         return loan;
      }

      private set;
   }

   public Boolean showBillingStatement { 
      get { 
         return (SHOW_STATEMENT_STATUS == loan.Status__c);
      }
   }

   public String officer { 
      get;
      set {
         if ((null == value) || (0 == value.trim().length())) {
            throw new ValidationException('Specify a loan officer');
         } else {
            officer = value;
            loan.Officer__c = [SELECT Id FROM User WHERE Name = :officer].Id;
         }
      }
   }

   public LoanController() {
      loanId = Utils.getIdPageParameter('id');
   }

   public void save() {
      upsert loan;
   }
}

'On-Demand' Advantages

In this second implementation, code is executed ‘On-Demand’.  For example, the loan property’s getter will not be run until the value is accessed by the VisualForce page. If our controller is used by a page that doesn’t access loan, the SOQL statement will not be executed at all. Using this pattern has the following advantages:

  • The user will get optimal performance, because we’re running fewer lines of code
  • Business logic is encapsulated within the appropriate method. The officer property, for example, owns its own validation by housing the logic in its setter, instead of relying on other methods.
  • Unit testing is more direct and readable. Instead of testing a ‘black box’ constructor that initializes all the properties at once, each one can be tested independently, directly, and completely.
  • Dependencies between properties are enforced. When the loanId changes, the next ‘get’ of the loan object will cause a re-query. Similarly, the officer property is correctly initialized any time our loan object changes.

A few more improvements were made as well…

  • We’re following the ‘Worse is Better’ principle. Functionality that is not required has been removed – such as showBillingStatement’s setter. The setter for the loan property has been changed to private. Providing a well-defined and restrictive interface will avoid bugs when future developers attempt to use your code in creative ways.
  • ‘Magic Numbers’ (in this case – a string) have been relocated as constants at the top of the file.

Finally, the name has been changed from LoanEditController to LoanController, because – thanks to these modifications – the controller can now be used for not only editing an existing loan, but also for creating a new loan, as well as providing other views of the loan object.

There’s room for more improvement, of course. Documentation, robust error handling, and unit tests have been left out for brevity.

Hopefully you’ve discovered a new practice or two that you can incorporate into your Salesforce work. Happy coding!