Services calling services

In the previous chapter, I described a reuse use case around a FormulaForce application feature that awards championship points to contestants. We imagined the first release of the application providing a Custom Button only on the Contestant detail page and then the second release also providing the same button but on the Race detail page.

In this part of the chapter, we are going to implement the respective service methods, emulating this development cycle and, in turn, demonstrating an approach to call between Service layer methods, passing the Unit Of Work correctly.

First, let's look at the initial requirement for a button on the Contestant detail page; the method in the ContestantService class looks like the following:

publicstaticvoidawardChampionshipPoints(Set<Id>contestantIds) {
  fflib_SObjectUnitOfWorkuow = Application.UnitOfWork.newInstance();

  // Apply championship points to given contestants
  Map<Integer, ChampionshipPoint__mdt>pointsByTrackPosition = new ChampionshipPointsSelector().selectAllByTrackPosition();
  for(Contestant__c contestant : new ContestantsSelector().selectById(contestantIds)) {
    // Determine points to award for the given position
    ChampionshipPoint__mdtpointsForPosition =pointsByTrackPosition.get(Integer.valueOf(contestant.RacePosition__c));
    if(pointsForPosition!=null) {
      // Apply points and register for udpate with uow
      contestant.ChampionshipPoints__c =
      pointsForPosition.PointsAwarded__c;
      uow.registerDirty(contestant);
    }
  }
}

This method utilizes the Selector pattern to query the database; this will be described in its own chapter later in this book. The method also uses a protected Custom Metadata Type object Championship Points, to look up the official championship points awarded for each position the contestants finished in the race. This Custom Metadata Type object and its records are provided with the source code for this chapter:

Services calling services

Tip

Custom Metadata records can be packaged and installed with the FormulaForce application without any further configuration by the customer. Since they belong to a protected Custom Metadata Type object, they will not be visible or editable to the user. Indeed, this information is tightly controlled by the Federation Internationale de l'Automobile (FIA) and is updated a minimum of once a year. In this form they can easily be edited using the Manage Records link under the Custom Metadata Type page under Setup, then upgraded when the customers install the latest version of the package or you Push the package to them. While this information could have been hard coded in Apex code, it is easier to manage and update in this form and demonstrates how internal configuration can be encoded using Custom Metadata Types and records.

The service is called from the ContestantController action method, which is bound to the button on the Contestant detail page (via a Visualforce page), as follows:

public PageReferenceawardPoints(){
  try {
    ContestantService.awardChampionshipPoints(new Set<Id> { standardController.getId() });
  }catch (Exception e) {
    ApexPages.addMessages(e);
  } 
  return null;
}

In the next release, the same behavior is required from the RaceController method to apply the logic to all contestants on the Race page. The controller method looks like the following:

public PageReferenceawardPoints(){
  try {
    RaceService.awardChampionshipPoints(new Set<Id> { standardController.getId() });
  }

  catch (Exception e){
    ApexPages.addMessages(e);
  } 
  return null;
}

In order to provide backward compatibility, the original custom button on the Contestant detail page and thus the service method used by the controller are to be retained. Because the original method was written with bulkification in mind to begin with, reusing the ContestantService logic from the new RaceService method is made possible.

The following code is what the RaceService.awardChampionshipPoints method looks like. Again, a Selector class is used to query the Race records and the Contestant child records in a single query such that the child records are also available:

public static void awardChampionshipPoints(Set<Id>raceIds){
  fflib_SObjectUnitOfWorkuow =Application.UnitOfWork.newInstance();

  // Query Races and contestants, bulkify contestants
  List<Contestant__c> contestants = new List<Contestant__c>();
  For(Race__c race : new RacesSelector().selectByIdWithContestants(raceIds)) { contestants.addAll(race.Contestants__r);}

  // Delegate to contestant service
  ContestantService.awardChampionshipPoints(uow, contestants);
  // Commit work
  uow.commitWork();
}

By using method overloading, a new version of the existing ContestantService.awardChampionshipPoints method was added to implement the preceding code without impacting other callers. The overloaded method takes as its first parameter the Unit Of Work (created in RaceService) and then the Contestant list.

Note

The problem with this additional method is that it inadvertently exposes an implementation detail of the service to potential non-service calling code. It would be ideal if Apex supported the concept of the protected keyword found in other languages, which would hide this method from code outside of the Service layer. The solution is to simply ensure that developers understand that such method overrides are only for use by other Service layer callers.

The following resultant code shows that the ContestantService methods now support both the new RaceService caller and the original ContestantController caller. Also, there is only one instance of Unit Of Work in either code path, and the commitWork method is called once all the work is done:

public static void awardChampionshipPoints(Set<Id>contestantIds) 
{
  fflib_ISObjectUnitOfWorkuow =Application.UnitOfWork.newInstance();

  // Apply championship points to selected contestants
  awardChampionshipPoints(uow, new ContestantsSelector().selectById(contestantIds));

  uow.commitWork();
}

public static void awardChampionshipPoints(
fflib_ISObjectUnitOfWorkuow, List<Contestant__c> contestants) 
{
  // Apply championship points to given contestants
  Map<Integer, ChampionshipPoint__mdt>pointsByTrackPosition = new ChampionshipPointsSelector().selectAllByTrackPosition();
  for(Contestant__c contestant : contestants) {
    // Determine points to award for the given position
    ChampionshipPoint__mdtpointsForPosition =pointsByTrackPosition.get(Integer.valueOf (contestant.RacePosition__c));
    if(pointsForPosition!=null) {
      // Apply points and register for udpate with uow
      contestant.ChampionshipPoints__c =pointsForPosition.PointsAwarded__c;
      uow.registerDirty(contestant);
    }
  }
}
..................Content has been hidden....................

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