Object-oriented programming

One of the big advantages of using Apex classes is the ability to leverage the power of OOP. OOP allows you to observe commonalities in data or behavior across your objects to share code or apply common processing across different objects.

An example of such a commonality in the Formula1 world are rules; every aspect of the sport has a set of rules and regulations to comply with, such as drivers owning a FIA Super License, the weight of the car they drive should be at least above a defined minimum, and ensuring that a team has not exceeded the maximum distance in testing their cars. Such compliances are checked regularly, both before and after a race.

Creating a compliance application framework

In our FormulaForce application, we want to create a compliance framework that will check these regulations across the different objects while also providing a consistent user interface experience for the end user to verify compliance. The initial requirement is to place a Verify Compliance button on the Detail Pages on all applicable objects.

The Visualforce Controller and Service code that perform the verification process should be generic and reusable across these objects, while the actual compliance-checking logic associated with each object will be specific. By using an Apex Interface, we can define this common requirement for the applicable Domain classes to implement.

In this section, we will implement this requirement using an Apex interface and a generic service that can be passed record IDs for different Domain objects across the application. Creating such a service avoids code duplication by having to repeat the logic of handling the results within the code of each Domain class and provides a centralized service for the controller to consume regardless of the object type in scope.

By using an Apex interface and a generic service, we separate the functional concerns of implementing compliance functionality as follows:

  • The Service layer code is concerned solely with executing the compliance-checking process through the interface
  • The Controller code is concerned with calling the service class method to execute the compliance check and then presenting the results to the user consistently
  • The Domain layer classes that implement the interface focus solely on validating the record data against the applicable rules according to the type of the object

Having this Separation of Concerns makes it easy to significantly modify or extend the ways in which the checking is invoked and presented to the user without having to revisit each of the Domain layer classes or conversely, modify the compliance logic of one object without impacting the overall implementation across the application.

An Apex interface example

Interfaces help express common attributes or behaviors across different domain classes. In this section, we will use an Apex interface to help expose the compliance-checking logic encapsulated in each of the applicable Domain classes.

In order to exercise and implement the compliance framework, the FormulaForce application has gained a few extra fields and objects. These objects, additional classes, and Visualforce pages are all included in the code samples of this chapter.

To summarize, the following steps have been taken to implement the compliance framework requirement. You can follow along with these or simply refer to the sample code of this chapter:

  1. Create a new Car object and tab.
  2. Create a new Weight field of type Number and length 6 on the Car object.
  3. Create a new FIA Super License field to the Driver object.
  4. Create a new Cars Domain class and CarsSelector Selector class.
  5. Create a new ComplianceService class and ICompliant interface.
  6. Create new Cars and Drivers Domain classes and implement the ICompliant interface.
  7. Update the Application class to implement a Domain class factory
  8. Create a new ComplianceService.verify method that utilizes the Domain factory.
  9. Create a user interface to access the service from the various objects that implement the interface.

Note

The last step will create two user interfaces, one for Salesforce Classic and one for Lightning Experience. The ComplianceController class provides a generic controller class for each specific Visualforce page per object. In addition the ComplianceChecker bundle provides a generic Lightning Component called Compliance Checker, which can be dropped on any object page.

The following sections describe in further detail the Apex coding from Step 5 onwards.

Step 5 – defining a generic service

The aim is to develop a common service to handle all compliance verifications across the application. So, a new ComplianceService class has been created with a verify method on it, which is capable of receiving IDs from various objects. If there are any verification errors, they will be thrown as part of an exception. If the method returns without throwing an exception, the given records are compliant. The following code snippet shows the creation of a new ComplianceService class with a verify method:

public class ComplianceService { 
  /**
   * Provides general support to verify compliance in the
   * application
   * @throws ComplianceException for any failures
   **/
  public static void verify(Set<Id>recordIds) {
    // Query the given records and delegate to the 
    //   corresponding Domain class to check compliance
    //   and report failures via ComplianceException
  }

Before we look further into how this service method can be implemented, take a look at the new ICompliant interface and the verifyCompliance method that will be implemented by the Drivers and Cars Domain classes:

public class ComplianceService {
  /** 
   * Interface used to execute compliance checking logic 
   * in each domain class
   **/
  public interface ICompliant {
    List<VerifyResult>verifyCompliance();
  }

  /**
   * Results of a compliance verification for a given record
   **/
  public class VerifyResult {
    public Id recordId;
    public String complianceCode;
    public Boolean passed;
    public String failureReason;
  } 
}

Step 6 – implementing the domain class interface

The Drivers Domain class implements the interface as follows (only a portion of the class is shown). It checks whether the FIA Super License field is checked and reports an appropriate compliance code and failure message if not:

public class Drivers extends ApplicationDomain
  implements ComplianceService.ICompliant {
   public List<ComplianceService.VerifyResult> verifyCompliance() {
     List<ComplianceService.VerifyResult> compliances = new List<ComplianceService.VerifyResult>();
     for(Driver__c driver : (List<Driver__c>) Records) {
         ComplianceService.VerifyResult license = 
            new ComplianceService.VerifyResult();
         license.ComplianceCode = '4.1';
         license.RecordId = driver.Id;
         license.passed = driver.FIASuperLicense__c;
         license.failureReason = 
         license.passed ? null : 
            'Driver must have a FIA Super License.';
         compliances.add(license);
     }
     return compliances; 
   }
}

The Cars domain class (of which only a portion of the class is shown) implements the verifyCompliance method as follows, checking whether the car is over the specified weight:

public class Cars extends ApplicationDomain
  implements ComplianceService.ICompliant {
  public List<ComplianceService.VerifyResult> verifyCompliance() {
    List<ComplianceService.VerifyResult> compliances =
      new List<ComplianceService.VerifyResult>();
    for(Car__c car : (List<Car__c>) Records) {
      // Check weight compliance
      ComplianceService.VerifyResult license = 
        new ComplianceService.VerifyResult();
      license.ComplianceCode = '4.1';
      license.RecordId = car.Id;
      license.passed = 
         car.Weight__c !=null && car.Weight__c>= 691;
      license.failureReason = license.passed ? null : 
        'Car must not be less than 691kg.';
      compliances.add(license);
    }
    return compliances; 
  }
}

Step 7 – the domain class factory pattern

The next step is to dynamically construct the associated Domain class based on the record IDs passed into the ComplianceService.verify method. This part of the implementation makes use of a Factory pattern implementation for Domain classes.

Note

A Wikipedia reference to the Factory pattern can be found at http://en.wikipediaedia.org/wiki/Factory_method_pattern.

The implementation uses the Apex runtime Id.getSObjectType method to establish SObjectType, and then via the Maps (shown in the following code), it determines the applicable Domain and Selector classes to use (to query the records).

The Apex runtime Type.newInstance method is then used to construct an instance of the Domain and Selector classes. The factory functionality is invoked by calling the Application.Domain.newInstance method. The Domain field on the Application class is implemented using the fflib_Application.DomainFactory class as follows:

public class Application {
  public static final fflib_Application.DomainFactory Domain = 
    new fflib_Application.DomainFactory(
      Application.Selector,
      // Map SObjectType to Domain Class Constructors
      new Map<SObjectType, Type> {
        Race__c.SObjectType =>Races.Constructor.class,
        Car__c.SObjectType =>Cars.Constructor.class,
        Driver__c.SObjectType =>Drivers.Constructor.class });
}

Note

As a developer you can of course instantiate a specific Domain class directly. However, this Domain factory approach allows for the specific Domain class to be chosen at runtime based on the SObjectType associated with the ID passed to the newInstance method. This is key to implementing the generic service entry point as shown in the next step. The preceding initialization of the Domain factory also passes in Application.Selector; this is another factory relating to classes that are able to query the database. The next chapter will cover the Selector pattern and the Selector factory in more detail.

Step 8 – implementing a generic service

The Application.Domain.newInstance method used in the following code is used to access the Domain instance factory defined in the last step.

This method returns fflib_ISObjectDomain, as this is a common base interface to all Domain classes. Then, using the instanceof operator, it determines whether it implements the ICompliant interface, and if so, it calls the verifyCompliance method. The following code shows the implementation of a generic service:

public class ComplianceService { 
  public static void verify(Set<Id>recordIds) {
    // Dynamically create Domain instance for these records
    fflib_ISObjectDomain domain =
        Application.Domain.newInstance(recordIds);
    if(domain instanceof ICompliant) {
      // Ask the domain class to verify its compliance
      ICompliantcompliantDomain = (ICompliant) domain;
      List<VerifyResult>verifyResults =         compliantDomain.verifyCompliance();
      if(verifyResults!=null) {
         // Check for failed compliances
         List<VerifyResult> failedCompliances = 
           new List<VerifyResult>();
         for(VerifyResultverifyResult : verifyResults) {
           if(!verifyResult.passed) {
                 failedCompliances.add(verifyResult);
              }
            }
         if(failedCompliances.size()>0) {
           throw new ComplianceException(
             'Compliance failures found.', failedCompliances);
           return;
         }
      }
      throw new ComplianceException(
        'Unable to verify compliance.', null);
    }
  }
}

Note

Note that, if the verify method is passed IDs for an object that does not have a corresponding Domain class, an exception is thrown from the factory. However, if a domain class is found but does not implement the interface, the preceding code simply returns without an error, though what the developer will do in this case is up to them.

Step 9 – using the generic service from a generic controller

In this step we will utilize the same ComplianceService.verify method from a Visualforce Controller and a Lightning Component controller. These user interfaces are also highly generic and thus easier to apply to new objects in future releases.

Generic Compliance Verification UI with Visualforce

A generic Visualforce Controller, ComplianceController, has been created with a verify action method that calls the service. Note that at no time have we yet referred to a specific Custom Object or Domain class, ensuring that this controller and the service are generic and able to handle objects of different types. The following code shows how the generic ComplianceService.verify method is being called from the ComplianceController.verify method:

public PageReference verify() {
  try {
    // Pass the record to the compliance service
    ComplianceService.verify(
      new Set<Id> { standardController.getId() });
    // Passed!
    ApexPages.addMessage(
      new ApexPages.Message(
         ApexPages.Severity.Info, 'Compliance passed'));
  }
  catch (Exception e) {
    // Report message as normal via apex:pageMessages
    ApexPages.addMessages(e);
    // Display additional compliance failure messages?
    if(e instanceofComplianceService.ComplianceException)
    {
      ComplianceService.ComplianceExceptionce
        (ComplianceService.ComplianceException) e;
      for(ComplianceService.VerifyResultverifyResult :ce.failures) {
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, String.format('{0} ({1})', new List<String> {verifyResult.failureReason,verifyResult.complianceCode })));
      }
    }
  }
  return null;
}

Visualforce pages are then created to bind this generic controller to Custom Buttons on the Driver and Car detail pages. This is the only specific reference made to objects that implement the ComplianceService.ICompliant interface and is only needed in order to create the Custom Buttons in Salesforce Classic. The following page for the Driver object defines a button that uses the generic ComplianceController.verify method:

<apex:pagestandardController="Driver__c"
   extensions="ComplianceController" action="{!verify}">
  <apex:pageMessages/>
  <apex:form>
    <apex:commandButton value="Cancel" action="{!cancel}"/>
  </apex:form>
</apex:page>

Generic Compliance Verification UI with a Lightning Component

The component described in this section can be placed on a Lightning Experience or Salesforce1 Mobile record pages for any object without any further configuration. The component utilizes the same Service as the previous Visualforce example to dynamically invoke the appropriate Domain class and apply the applicable verification process.

Note

Lightning Components will be covered in more detail in a later chapter. This component demonstrates one of the many places where components can be used to extend Lightning Experience and other aspects of the platform.

The following code shows the Apex Controller for the component:

public with sharing class ComplianceCheckerComponent {

@AuraEnabled
public static List<String> verify(Id recordId) {
  try {
    // Verify the given record for compliance
    ComplianceService.verify(newSet<Id> { recordId });
    // Success, all good!
    Return null;
  } catch (Exception e) {
     // Report message as normal via apex:pageMessages
     List<String> messages = new List<String> { e.getMessage() };
     // Display additional compliance failure messages?
     If(e instanceofComplianceService.ComplianceException) {
       ComplianceService.ComplianceExceptionce =(ComplianceService.ComplianceException) e;
       for(ComplianceService.VerifyResultverifyResult : ce.failures) {
         messages.add(String.format('{0} ({1})', 
            new List<String> { 
              verifyResult.failureReason,
              verifyResult.complianceCode }));                   
       }
     }
     return messages;
  }                
}

The following code shows the component markup, JavaScript controller and helper:

ComplianceChecker.cmp:

<aura:component implements="force:hasRecordId,flexipage:availableForRecordHome" controller="ComplianceCheckerComponent" access="global">
   <aura:dependency resource="markup://force:editRecord"type="EVENT" />
   <aura:attribute name="category" type="String" default="success" />
   <aura:attribute name="messages" type="String[]"/>
   <aura:handler name="init" value="{!this}"action="{!c.onInit}"/>
   <aura:handler event="force:refreshView"action="{!c.onRefreshView}"/>
   <div class="{!'slds-box slds-theme—' + v.category}">
     <aura:iteration items="{!v.messages}"var="message">
       <p><ui:outputText value="{!message}"/></p>
     </aura:iteration>
   </div>
</aura:component>

Tip

The preceding code handles the force:refreshView event sent by the Lighting Experience framework when the user edits records. Because the flexipage:availableForRecordHome interface is specified, this component can only be placed on record pages.

ComplianceCheckerControler.js:

({
  onInit : function(component, event, helper) {
    helper.verifyCompliance(component, event, helper);
  },
  onRefreshView : function(component, event, helper) {
    helper.verifyCompliance(component, event, helper);
  }
})

ComplianceCheckerHelper.js:

({
	verifyCompliance : function(component) {
      var action = component.get("c.verify");
      action.setParams({ "recordId" : component.get("v.recordId") });
      action.setCallback(this, function(response) {
        if(response.getState() === 'SUCCESS') {
          var messages = response.getReturnValue();
          if(messages!=null) {
            component.set("v.category", "error");
            component.set("v.messages", messages);
          } else {
            component.set("v.category", "success");
            component.set("v.messages", ["Verified compliance" ]);
          }
        }                
      });
      $A.enqueueAction(action);        
	}
})

Unlike the Visualforce page, no specific reference to an object is made, allowing the component to be used on any page.

Tip

If you wanted to restrict the visibility of the component within the Lightning App Builder tool where users customize Lightning Experience and Salesforce1 Mobile, you can utilize the sfdc:objects tag in your .design file.

<sfdc:objects><sfdc:object>Driver__c</sfdc:object><sfdc:object>Car__c</sfdc:object></sfdc:objects>

Summarizing compliance framework implementation

Using an Apex Interface and the Factory pattern, a framework has been put in place within the application to ensure that the compliance logic is implemented consistently. This is a simple matter of implementing the Domain class interface and creating the Visualforce page or using the generic Lightning component, to enable the feature for other applicable objects in the future.

Note

As this has been implemented as a service, it can easily be reused from a Batch Apex or Scheduled context as well.

The following screenshot illustrates the Verify Compliance button on the Driver object. Ensure that the FIA Super License field is unchecked before pressing the button.

Summarizing compliance framework implementation

The following errors should be displayed:

Summarizing compliance framework implementation

The following screenshot shows the Verify Compliance button on the Car object:

Summarizing compliance framework implementation

The following errors are shown by the generic controller defined/created previously:

Summarizing compliance framework implementation

The following shows the Compliance Checker Lightning Component within the Lightning App Builder tool being placed on the Driver page:

Summarizing compliance framework implementation

The following shows the Compliance Checker component in place on the Driver page. In Lightning Experience, there is no button, the component is embedded in the page and reacts to the field updates the user makes. So, the verification is performed immediately.

Summarizing compliance framework implementation
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.218.45