The Domain layer is positioned with respect to visibility and dependency below the Service layer. This in practice means that Domain classes should not be called directly from the execution context code, such as Visualforce Controllers, Lightning Component Controllers or Batch Apex, as it is the Service layer's responsibility to be the sole entry point for business process application logic.
That being said, we saw that the Domain layer also encapsulates an object's behavior as records are manipulated by binding Apex Trigger events to methods on the Domain class. As such, Apex Triggers technically form another point of invocation.
Finally, there is a third caller type for the Domain layer, and this is another Domain class. Restrict your Domain class callers to the following contexts only:
fflib_SObjectDomain.handleTrigger
method.Races
Domain class can have some functionality it implements that also requires the use of a method on the Contestants
Domain class.Note that it is entirely acceptable to have a Domain class with no Service class code referencing it at all, such as in the case where the Domain class solely implements the code for use in an Apex Trigger context and doesn't contain any custom Domain methods. Then, the only invocation of the Domain class will be indirect via the trigger context.
While implementing the compliance framework earlier in this chapter, we saw how a Domain class can be dynamically created within a generic service method. Typically, Domain classes are created directly. In this section, we will see a couple of examples of these.
The following code shows the Domain class constructor being passed records from a Selector class, which returns List<Contestant__c>
for the purposes of this chapter. Notice that the Unit Of Work is created and passed to the Domain class custom method so that this method can also register work of its own to be later committed to the database:
public class ContestantService { public static void awardChampionshipPoints( Set<Id>contestantIds) { fflib_SObjectUnitOfWork uow = Application.UnitOfWork.newInstance(); // Apply championship points to given contestants Contestants contestants = new Contestants( new ContestantsSelector().selectById(contestantIds)); contestants.awardChampionshipPoints(uow); uow.commitWork(); } }
You might have noticed that this example reworks the Service example from the previous chapter by refactoring the code and calculating the points closer to the object for which the code directly applies by basically moving the original code to the Domain class.
This following second example also reworks the code from the previous chapter to leverage the Domain layer. In this case, the RaceService
class also implements an awardChampionshipPoints
method. This can also benefit from the Contestants
Domain class method. Note how easy it is to reuse methods between the Service and Domain layers, as both ensure that their methods and interactions are bulkified.
public class RaceService { public void awardChampionshipPoints(Set<Id>raceIds) { fflib_SObjectUnitOfWork uow = Application.UnitOfWork.newInstance(); List<Contestant__c> contestants = new List<Contestant__c>(); for(Race__c race : new RacesSelector().selectByIdWithContestants(raceIds)) { contestants.addAll(race.Contestants__r); } // Delegate to Contestant Domain class new Contestants(contestants).awardChampionshipPoints(uow); // Commit work uow.commitWork(); } }
In the following example (also included in the sample code for this chapter), a new Custom Object is created to represent the teams that participate in the races. The Driver object has gained a new Lookup field called Team to associate the drivers with their teams.
Leveraging the compliance framework built earlier, a Verify Compliance button for the Team records is also added to provide a means to check certain aspects of the team that are compliant (such as the maximum distance cars can cover during testing) as well as whether all the drivers in this team are also still compliant (reusing the existing code).
The following components have been added to the application to support this example:
Teams
Domain classBeing able to call between the Domain layer classes permits the driver compliance-checking code to continue to be reused at the Driver level as well as from the Team level. This Domain class example shows the Teams
Domain class calling the Drivers
Domain class:
public class Teams extends fflib_SObjectDomain implements ComplianceService.ICompliant { public List<ComplianceService.VerifyResult> verifyCompliance() { // Verify Team compliance List<ComplianceService.VerifyResult>teamVerifyResults = new List<ComplianceService.VerifyResult>(); for(Team__c team : (List<Team__c>) Records) { ComplianceService.VerifyResulttestingDistance = new ComplianceService.VerifyResult(); testingDistance.ComplianceCode = '22.5'; testingDistance.RecordId = team.Id; testingDistance.passed = team.TestingDistance__c!=null ? team.TestingDistance__c <= 15000 : true; testingDistance.failureReason = testingDistance.passed ? null : 'Testing exceeded 15,000km'; teamVerifyResults.add(testingDistance); } // Verify associated Drivers compliance teamVerifyResults.addAll( new Drivers( new DriversSelector().selectDriversByTeam( new Map<Id, SObject(Records).keySet())) .verifyCompliance()); return teamVerifyResults; } }
The bulkification guideline applied to the Domain layer is being leveraged in the preceding code, as the Drivers
Domain class logic was reused directly, with no changes, from the Teams
Domain class. Also note that the implementation of the Drivers
Domain class was and is still unaware of the split of drivers by team.
The following screenshot shows the new Team object and Compliance Checker component added to the Team page using Lighting App Builder. The team record and related driver record have compliance issues. For the team record an invalid Testing Distance value greater than 15,000 km. The FIA Super License field on the Driver record for Lewis Hamilton, which is unchecked, is not shown.
18.226.177.86