Extending the application logic with Apex interfaces

An Apex Interface can be used to describe a point in your application logic where custom code written by Developer X can be called. For example, in order to provide an alternative means to calculate championship points driven by Developer X, we might expose a global interface describing an application callout that looks like this:

global class ContestantService {global interface IAwardChampionshipPoints {
    void calculate(List<Contestant__c> contestants);
  }
}

By querying Custom Metadata records from the Callouts custom metadata type, which has been included in the source code for this chapter, code in the application can determine whether Developer X has provided an implementation of this interface to call instead of the standard calculation code.

Using Custom Metadata is an excellent use case for this sort of requirement, since you can declare the callouts your package supports by packaging records. Then, by making certain fields subscriber editable you can allow the subscriber (Developer X) to configure those callouts. This approach in contrast with Custom Settings, avoids additional configuration and risk of misconfiguration.

Tip

Partners extending your package can also package this configuration with their Apex class. Thus administrators installing such partner extension packages have zero configuration to perform to activate the callout contained within.

This screenshot shows how the Callouts Custom Metadata Type is defined. Notice how the Apex Class and Apex Class Namespace Prefix fields are subscriber editable:

Extending the application logic with Apex interfaces

The following record is defined by you the packager developer to effectively declare that Award Championship Points callout exists and can be configured. This should also be included in your package:

Extending the application logic with Apex interfaces

Tip

Notice how the Custom Metadata Type fields supports Developer X providing a class name that is either fully qualified (perhaps from an extension package, for example, fforceext.SimpleCalc) or a local class (defined in the subscriber org), for example, DeveloperXCustomCalc.

The following code is a Selector method (see CalloutsSelector) class that reads the registered interface implementations. It uses the Type.forName method to construct applicable Apex types and returns a Map of Apex classes implementing each interface:

public Map<Type, Type> selectAllCallouts() {        
    // Query custom metadata records for callouts
    Map<Type, Type> calloutsByInterfaceType = new Map<Type, Type>();                    
    for(Callout__mdt record :Database.query(newQueryFactory().toSOQL())) {
       if(Callout__mdt.ApexCLass__c!=null) {
           // Namespace of the interface is that //   of the custom metadata type 
           Type interfaceType = Type.forName(record.NamespacePrefix, record.InterfaceType__c);
             // Implementing class can optionally specify //   the namespace if needed
             Type implType = Type.forName(record.ApexCLassNamespacePrefix__c,record.ApexCLass__c);
             calloutsByInterfaceType.put(interfaceType, implType);
           }
       }       
       return calloutsByInterfaceType;
    }

The following code uses a new Application.Callout class factory, which is not shown here but is included in the source code of this chapter. This is a simple class factory that uses the above Selector method and exposes the newInstance method to provide an easy way to instantiate the classes Developer X has registered. The following method in the Contestants Domain class now looks like this:

public void awardChampionshipPoints(fflib_ISObjectUnitOfWork uow)
{
    // Custom implementation configured by Developer X?
    Object registeredInterfaceImpl =Application.Callouts.newInstance(ContestantService.IAwardChampionshipPoints.class);
    if(registeredInterfaceImpl instanceofContestantService.IAwardChampionshipPoints) {
      // Cast the interface to call the calculate method
      ContestantService.IAwardChampionshipPointsawardChampionshipPoints =(ContestantService.IAwardChampionshipPoints)registeredInterfaceImpl;
      // Invoke the custom method from Developer X
      awardChampionshipPoints.calculate(Records);
      // Mark dirty on behalf of Developer X
      for(Contestant__c contestant : (List<Contestant__c>) Records) {
         uow.registerDirty(contestant);
      }
      return;
    }
    // Continue with standard implementation...

In the subscriber org or the test development org, Developer X can then implement this interface as follows and configure the Apex Class field on the Callouts record corresponding to the Award Championship Points calllout. The next time the award championship points service is called, this custom logic will be called instead:

Extending the application logic with Apex interfaces

The following is a simple Application Callout implementation that assigns points according to the race position rather than using the default calculation:

public class SimpleCalc
  implements fforce.ContestantService.IAwardChampionshipPoints
{
  public void calculate(List<fforce__Contestant__c> contestants)
  {
    // Very simple, points equals race position
    for(fforce__Contestant__c contestant : contestants)
      contestant.fforce__ChampionshipPoints__c =contestant.fforce__RacePosition__c;
  }
}

Some aspects to consider when exposing global Apex interfaces are as follows:

  • You cannot modify global interfaces once published by uploading them within a release managed package.
  • You can extend a global interface from another global interface, for example IAwardChampionshipPointsExt extends IAwardChampionshipPoints.
  • From a security perspective consider carefully what you pass into an Application Callout via your Apex Interface methods, especially if the parameters are objects whose values are later referenced by the application code. Be sure that you are happy for the Application Callout to make any changes to this object or data. If you're not comfortable with this, consider cloning the object before passing it as a parameter to the interface method.
..................Content has been hidden....................

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