wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Pattern in your Apex Controller


A (software) design pattern is a general, reusable solution to a commonly occurring problem withing a given context. Christoper Alexander inspired the Gang of four to apply pattern to software and enumerate 23 classic software pattern.

This article discusses how to use some of them in the context of Apex controllers.

The context: Same same, but different

You are creating an application to support construction on force.com that will serve multiple countries. Part of the requirements is to compute a risk score for any given project. While ISO standards form the foundation of the assessment, each jurisdiction has some specialties that alter the logic eventually.

This is just one of the requirements, you have many more that follow the pattern Same Same - But different.

Patterns used

Besides those, you want to know Apex Enterprise Patterns. Go Trailhead and learn

The details

The Apex controller isn't used to retrieve records, that's the job of the Lightning Data Service. It is however used for auxiliary information, like the computation of the risk score or check which part of your component should be rendered or or or. Most of the functionality is needed elsewhere too (like compute risk score in a trigger), so coding it into a @AuraEnabled static method doesn't make much sense.

Therefore the Apex controller is implemented as a Facade. No actual logic is implemented, but execution is delegated to specific classes (sample below).

The controller shouldn't contain logic to pick the concrete class, since each new country you start business in would require a change to the controller. Picking the right class is left to a factory. A common approach for factories is to have a factory class with one factory method. You can design that class static or to be instantiated, depending on the amount of context your factory needs. In my case I used a static RiskAssessmentFactory class with a single static method RiskAssessor getRiskAssessor(String countryId) that would return....

An instance of an interface (e.g. RiskAssessor)! This is an important element of maintainability. A factory method should always return an (instance of an) interface. Not a base class or one specific class, but an interface. Returning a concrete class defeats the very purpose of the factory pattern, returning a base class limits the flexibility of your solution. Factory and Interface belong together like Stand and Ollie, Caesar and Cleopatra or peanut butter and jelly.

Repeat after me:

A factory method returns an instance of an interface

When you define your factory without interfaces, on each call a kitten must die and an unicorn cries.

Where does the metadata fit in?

Analyzing the business requirements, you notice that the rules are quite similar, just some of the factors are different. E.g. in one jurisdiction number of stories is multiplied by 1.2, in another by 1.5 and in another not considered (= multiplied by 0).

So you design a custom metadata object (don't make the mistake and use a custom object - it will only add headache to your sandbox testing) and create records for your countries, so the same class can cater for multiple jurisdictions. Your factory will read that meta data before instantiating a class that implements the RiskAssesor interface.

To maintain your flexibility, after all you suspect that the building code for Zamunda is vastly different, the metadata record contains a field CustomRiskClass. When that field isn't empty your factory will not instantiate the common base class, but use this class. Storing the class name in metadata allows you to keep your country specific implementations in their own package.

A country package would as a minimum contain the meta data record. If the need for a custom class arises, that class would be in the country package too.

A sample

Fair warning: that code is typed here, probably not ready for copy and paste.

public interface RiskAssessor {
 RiskAssessor setMetadata(CountryRiskConfig_mdt countryMetadata);
 int isoRisk(ProjectPlan__c theProject);
 String countryRiskLevel(ProjectPlan__c theProject);
 RiskResult(String projectPlanId);
}

public class RiskAssessmentFactory {
 static RiskAssessor getRiskAssessor(String countryId) {
  RiskAssessor result;
  CountryRiskConfig_mdt countryMetadata = getCountryMeta(countryId);
  if (countryMetadata.CustomRiskClass != null) {
   result = (RiskAssessor) Type.forName(countryMetadata.CustomRiskClass).newInstance();
  } else {
   result = new RiskAssessorDefault;
  }
  result.setMetadata(countryMetadata);
  return result;
 }
}

public virtual class RiskAssessorDefault implements RiskAssessor {
 // TODO: implement interfaces here
}

The RiskAssessorDefault class is defined using the virtual keyword, so you can subclass it as need arises. To be clear: the subclassing helps to avoid duplicate code, it doesn't break the need for interfaces.

As usual: YMMV


Posted by on 29 December 2018 | Comments (0) | categories: Apex Salesforce Software

Comments

  1. No comments yet, be the first to comment