Expanding On the Factory Pattern
Posted: September 06, 2022

Expanding On the Factory Pattern

Table of Contents:

  • An Example Problem: Caching Dependencies

  • Keeping Track of Dependencies In The Factory

    • A Brief Interlude: Map Performance
  • Returning To Registering Singletons

    • Refreshing Singleton Instances Naively
    • Refreshing Singleton Instances By Type, Pseudo-Explicitly
  • Wrapping Up

I recently revised the original Factory pattern article, and wanted to write a follow-up after a few years’ spent reflecting on its usage within Apex. In particular, I’d like to talk a little bit about the Singleton pattern, and how with a small amount of work we can enhance the existing Factory class in order to gracefully create cached instances of classes where a large amount of work goes into constructing them.

An Example Problem: Caching Dependencies

While it’s atypical for objects to be logic-heavy in their constructors, sometimes this is a necessary evil — whether it’s because we need to run a bunch of queries, or because dependencies need to be pre-filtered in a CPU-intensive way, there are plenty of exceptions to this rule for constructors. Consider the following example class:

public class CustomObjectRepository {
  private final List<ExampleSObject__c> exampleSObjects;

  public CustomObjectRepository() {
    // let's assume we have "TextKey__c" as a custom field on this object
    this.exampleSObjects = [SELECT Id, Name, TextKey__c FROM ExampleSObject__c];
  }

  public List<ExampleSObject__c> getFiltered(String filter) {
    List<ExampleSObject__c> matchingObjects = new List<ExampleSObject__c>();
    for (ExampleSObject__c potentialMatch : this.exampleSObjects) {
      if (potentialMatch.TextKey__c.contains(filter)) {
        matchingObjects.add(potentialMatch);
      }
    }
    return matchingObjects;
  }
}

There’s nothing particular “evil” about this very simple class, but it does make a query in its constructor. If we wanted to write a test showing how this might be an issue as this class’s responsibilities expand:

@IsTest
private class CustomObjectRepositoryTests {

  @IsTest
  static void instancesAreCachedByTheFactory() {
    CustomObjectRepository customObjectRepo = Factory.getFactory().getCustomObjectRepository();
    // now retrieve second instance
    CustomObjectRepository secondRepo = Factory.getFactory().getCustomObjectRepository();

    System.assertEquals(1, Limits.getQueries()); // System.AssertException: Assertion Failed: Expected: 1, Actual: 2
    System.assertEquals(customObjectRepo, secondRepo);
  }
}

Well, this is quite the pickle. We could resort to making the exampleSObjects list static final. That would initialize it only once, and would quickly solve this particular problem:

public class CustomObjectRepository {
  private static final List<ExampleSObject__c> exampleSObjects = preloadRecords();

  public List<ExampleSObject__c> getFiltered(String filter) {
    List<ExampleSObject__c> matchingObjects = new List<ExampleSObject__c>();
    for (ExampleSObject__c potentialMatch : exampleSObjects) {
      if (potentialMatch.TextKey__c.contains(filter)) {
        matchingObjects.add(potentialMatch);
      }
    }
    return matchingObjects;
  }

  private static List<ExampleSObject__c> preloadRecords() {
    // let's assume we have "TextKey__c" as a custom field on this object
    return [SELECT Id, Name, TextKey__c FROM ExampleSObject__c];
  }
}

However, we shouldn’t be so quick to assume that static variables are always the way to handle this use-case; in particular, if the object construction is not only limit-heavy, but CPU heavy, and we are using it both synchronously and async, we’d be burning CPU time for no reason to reconstruct those static final variables in every thread. There are additional complications that affect both the instance and static versions of this exampleSObjects property, but I’ll limit the discussion to the subject raised for the purposes of focus. If another class in our hierarchy is making use of this object, and has it cached internally as a dependency, it could be quite dangerous to keep track of something like exampleSObjects via static properties.

Prior to starting to dig into the implementation, let’s pull a bit more on this thread by adding an additional method signature to the CustomObjectRepository (returning to the instance property version of the class):

public class CustomObjectRepository {
  private final List<ExampleSObject__c> exampleSObjects;
  private final Set<String> priorFilters = new Set<String>();

  public CustomObjectRepository() {
    this.exampleSObjects = [SELECT Id, Name, TextKey__c FROM ExampleSObject__c];
  }

  public Boolean hasFilterBeenApplied(String filter) {
    return this.priorFilters.contains(filter);
  }

  public List<ExampleSObject__c> getFiltered(String filter) {
    List<ExampleSObject__c> matchingObjects = new List<ExampleSObject__c>();
    for (ExampleSObject__c potentialMatch : this.exampleSObjects) {
      if (potentialMatch.TextKey__c.contains(filter)) {
        matchingObjects.add(potentialMatch);
      }
    }
    this.priorFilters.add(filter);
    return matchingObjects;
  }
}

Now, if we were using a static final Set to keep track of filters (which would be necessary if exampleSObjects was also static as in the prior example), making re-use of this object between sync and async contexts would present issues — hasFilterBeenApplied would get re-set between the sync and async versions, even if the CustomObjectRepository itself was the same instance between those two threads. Imagine if hasFilterBeenApplied was responsible for:

  • sending an update to an external system
  • emailing a customer
  • sending a slack notification to a public channel that there was a problem

In other words — while this is a simple example, it’s easy to imagine how a false negative here could lead to bugs, complaints, or general system weirdness. So what can we do to fix the problem?

Keeping Track of Dependencies In The Factory

Ironically, now that I’ve railed on the static keyword for a bit, it’s time to use it properly to our advantage within the Factory itself:

public virtual class Factory {
  private final Map<Type, Object> singletonTypeToInstance = new Map<Type, Object>();

  private static Factory factory;

  // ... etc ...

  // since the Factory instance itself is shared statically
  // as long as dependencies get stored in class instances
  // downstream, they will remain the same while using
  // the singletonTypeToInstance map
  public static Factory getFactory() {
    if (factory == null) {
      factory = new Factory();
    }

    return factory;
  }

  public CustomObjectRepository getCustomObjectRepository() {
    if (this.singletonTypeToInstance.containsKey(CustomObjectRepository.class)) {
      return (CustomObjectRepository) this.singletonTypeToInstance.get(CustomObjectRepository.class);
    }
    this.singletonTypeToInstance.put(CustomObjectRepository.class, new CustomObjectRepository());
    return this.getCustomObjectRepository();
  }
}

Now our test passes! As with the note above, this means that if a class sets CustomObjectRepository as a dependency from the factory prior to going async, the object doesn’t need to re-query in the async context because it’s already been setup as a Singleton in the synchronous context.

Our new issue is that the code necessary to register a singleton is a bit ugly, even with the recursion to save on lines. The balance that we need to strike is in structuring our code such that the factory only needs to initialize the dependency itself once. In addition to that, we don’t want the factory to have to check every class that we’re looking to initialize.

The best possible version of this requires an additional constructor in CustomObjectRepository (if it ends up making use of additional dependencies, at any rate):

// currently optional
// since CustomObjectRepository has no dependencies
public class CustomObjectRepository {
  private final Factory factory;
  private final List<ExampleSObject__c> exampleSObjects;
  private final Set<String> priorFilters = new Set<String>();

  public CustomObjectRepository() {
    this.factory = Factory.getFactory();
    this.exampleSObjects = [SELECT Id, Name, TextKey__c FROM ExampleSObject__c];
  }
}

Which then allows us to simplify the Factory code quite a bit:

public virtual class Factory {
  private final Map<Type, Object> singletonTypeToInstance = new Map<Type, Object>();

  // etc ...

  public CustomObjectRepository getCustomObjectRepository() {
    return (CustomObjectRepository) this.getOrRegisterSingleton(CustomObjectRepository.class);
  }

  private Object getOrRegisterSingleton(Type instanceType) {
    if (this.singletonTypeToInstance.containsKey(instanceType)) {
      return this.singletonTypeToInstance.get(instanceType);
    }
    Object discreteInstance = instanceType.newInstance();
    this.singletonTypeToInstance.put(instanceType, discreteInstance);
    return discreteInstance;
  }
}

A Brief Interlude: Map Performance

Note that the simplification in the prior example is without our “one line saver” of the recursive version of getOrRegisterSingleton; no need to do the extra map containsKey call for no reason. Now, I didn’t discuss this in my post on Idiomatic Apex, but I know there are some mixed schools of thought on the preferred way to access / set variables in a map. This is something that bears talking about because of what I just said; namely, that we’ve added a line above instead of going with something shorter to avoid having to call containsKey twice. Why?

There are two common ways to access map variables — I very much prefer the if/else setup when interacting with maps, and for the first time I’d like to show you why you might change your mind about the other approach. Consider two tests:

// I updated this one variable and ran each test several times
// and report the findings in a table below this excerpt
private static final Integer STRESSTEST_ITERATIONS = 100;
private static final Map<Integer, Object> STRESSTEST_MAP = new Map<Integer, Object>{
  1 => 15,
  80 => 30,
  1500 => 45,
  4791 => 60,
  5789 => 75,
  7834 => 90,
  9322 => 105
};

@IsTest
static void stresstestIfElseMapAccess() {
  for (Integer index = 0; index < STRESSTEST_ITERATIONS; index++) {
    Object potentialObject;
    if (STRESSTEST_MAP.containsKey(index)) {
      potentialObject = STRESSTEST_MAP.get(index);
    } else {
      potentialObject = index;
      STRESSTEST_MAP.put(index, potentialObject);
    }
  }
}

@IsTest
static void stresstestGetPutMapAccess() {
  for (Integer index = 0; index < STRESSTEST_ITERATIONS; index++) {
    Object potentialMatch = STRESSTEST_MAP.get(index);
    if (potentialMatch == null) {
      potentialMatch = index;
    }
    STRESSTEST_MAP.put(index, potentialMatch);
  }
}

Over a large enough map, the performance differences between these two setups is less noticeable:

Number of iterations If Else ms Get Put ms
100 5 16
1000 15 27
10000 136 177
100000 1054 1289
1000000 10219 10823

But it’s important to note that over small map sizes in particular the differences in access are quite noticeable. This is something that remains top of mind for me; for better or worse, the team I work on routinely has to deal with maps with over 1 million key-value pairs (where the relative performance is essentially equal between map access idioms); in Apex Rollup, on the other hand, it’s much more common to be using maps with key-value pairs in size between 0 and 1000 — in both instances, in both my open source work and the work we do as a team, those same maps are hammered by access (think dozens of calls to the same map). At the kind of record count we’re talking about — which is extremely common in Apex — any kind of repeated map access is going to quickly add up to significant performance losses if you prefer the get/put approach. By the same logic, you might get a ton of quick wins if you’re dealing with slow performing code and you can swap to the if/else paradigm!

Just a little food for thought. 🧪

Returning To Registering Singletons

Returning to the factory, there are two additional considerations for us to ponder:

  • since the Factory instance is, itself, statically cached: what recourse do we have when objects are transitioning between threads and we want the factory to retain its state?
  • on certain code paths, an object that registers itself as a singleton — particularly a repository responsible for returning database records — may need to re-do the query(s) being performed in order to fetch up to date information from the database. How can clients of these dependencies best communicate the need to bust the cache — and how can we avoid breaking encapsulation while doing so? There are two possible encapsulation-breaking behaviors here:
    • downstream client code having to call “cache busting” code may indicate that those objects “know too much” about the internals of any given repository instance
    • is it the factory’s responsible to register singletons, or is it the dependent object(s)‘s responsibility? Who should know what?

The first question is a relatively easy one to answer: to ensure Type.forName() compatibility (which requires any given object to have a zero-argument constructor), we can cache the factory instance itself:

public class AnExampleMadeInTheFactory {
  private final Factory factory;
  private final CustomObjectRepository customObjectRepo;
  // etc, other cached dependencies

  public AnExampleMadeInTheFactory() {
    this(Factory.getFactory());
  }

  public AnExampleMadeInTheFactory(Factory factory) {
    this.factory = factory;
    this.customObjectRepo = this.factory.getCustomObjectRepository();
    // etc, setting up of other dependencies
  }
}

Since the singletonTypeToInstance map isn’t static within the factory itself, any object with this kind of zero-arg constructor doesn’t have to worry about its dependencies being mistakenly refreshed between sync and async contexts (threads).

The second question is a bit thornier — and, to be honest, more subjective. My personal opinion on the subject is that if objects, like our CustomObjectRepository have the tendency to be cached as a singleton, they should also (via the factory) expose a cache-busting mechanism themselves (which can be a no-op if the object isn’t cached):

public class AnExampleMadeInTheFactory {
  private final Factory factory;
  // note - this dependency can no longer be "final"
  private CustomObjectRepository customObjectRepo;

  private static final String MATCHING = 'matching';

  public AnExampleMadeInTheFactory() {
    this(Factory.getFactory());
  }

  public AnExampleMadeInTheFactory(Factory factory) {
    this.factory = factory;
    this.customObjectRepo = this.factory.getCustomObjectRepository();
    // etc, setting up of other dependencies
  }

  public List<ExampleSObject__c> someCallingCode() {
    List<ExampleSObject__c> matchingRecords = this.customObjectRepo.getFiltered(MATCHING);
    for (ExampleSObject__c matchingRecord : matchingRecords) {
      matchingRecord.TextKey__c = 'non-' + MATCHING;
    }
    update matchingRecords;

    return this.customObjectRepo.getFiltered(MATCHING);
  }
}

If we were to write a test for AnExampleMadeInTheFactory and wanted to assert that each record’s TextKey__c now equaled “non-matching”, that would be a problem. There are a few ways to “solve” this particular problem, each with its own pitfalls. I’ll show two, within the factory itself:

Refreshing Singleton Instances Naively

I’ll refer to this as the “naive” approach, or perhaps the “better encapsulated” one, because it doesn’t require the factory or calling code to know anything special about each dependency:

public virtual class Factory {
  private final Map<Type, Object> singletonTypeToInstance = new Map<Type, Object>();

  // etc ...

  public CustomObjectRepository getCustomObjectRepository() {
    return (CustomObjectRepository) this.getOrRegisterSingleton(CustomObjectRepository.class);
  }

  public Object refreshSingleton(Object singletonInstance) {
    for (Type singletonType : this.singletonTypeToInstance.keySet()) {
      Object potentiallyMatchingInstance = this.singletonTypeToInstance.get(singletonType);
      if (potentiallyMatchingInstance == singletonInstance) {
        singletonInstance = singletonType.newInstance();
        this.singletonTypeToInstance.put(singletonType, singletonInstance);
        break;
      }
    }
    return singletonInstance;
  }

  private Object getOrRegisterSingleton(Type instanceType) {
    if (this.singletonTypeToInstance.containsKey(instanceType)) {
      return this.singletonTypeToInstance.get(instanceType);
    }
    Object discreteInstance = instanceType.newInstance();
    this.singletonTypeToInstance.put(instanceType, discreteInstance);
    return discreteInstance;
  }
}

And then:

public class AnExampleMadeInTheFactory {
  // etc. ..

  public List<ExampleSObject__c> someCallingCode() {
    List<ExampleSObject__c> matchingRecords = this.customObjectRepo.getFiltered(MATCHING);
    for (ExampleSObject__c matchingRecord : matchingRecords) {
      matchingRecord.TextKey__c = 'non-' + MATCHING;
    }
    update matchingRecords;

    this.customObjectRepo = (CustomObjectRepository) this.factory.refreshSingleton(this.customObjectRepo);
    return this.customObjectRepo.getFiltered(MATCHING);
  }
}

Having to cast is a gross necessity here, unless (until?) Apex reintroduces support for generics.

Refreshing Singleton Instances By Type, Pseudo-Explicitly

I’ve talked before (in setTimeout() & Implementing Delays and Enum Apex Class Gotchas) about “the great Type hack” in Apex, since we don’t have access to the .class property off of any object by default, which takes advantage of the default toString() implementation for all Apex classes:

// elsewhere in our code base:
AnExampleMadeInTheFactory example = Factory.getFactory().getExample();
System.debug(String.valueOf(example)); // produces "AnExampleMadeInTheFactory: ..."

Again, since toString() is easy to override for a class, making use of this hack should be used with caution, but I’ll reprint it here for posterity within this example:


public virtual class Factory {
  private final Map<Type, Object> singletonTypeToInstance = new Map<Type, Object>();

  // etc ...

  public CustomObjectRepository getCustomObjectRepository() {
    return (CustomObjectRepository) this.getOrRegisterSingleton(CustomObjectRepository.class);
  }

  public Object refreshSingleton(Object singletonInstance) {
    Type singletonType = this.getTypeDynamically(singletonInstance);
    if (this.singletonTypeToInstance.containsKey(singletonType)) {
        singletonInstance = singletonType.newInstance();
        this.singletonTypeToInstance.put(singletonType, singletonInstance);
    }
    return singletonInstance;
  }

  private Object getOrRegisterSingleton(Type instanceType) {
    // same as above ...
  }

  private Type getTypeDynamically(Object classInstance) {
    String className = String.valueOf(classInstance)
      .split(':')[0];
    return Type.forName(className);
  }
}

On the one hand, the code comes out cleaner by virtue of not having to blindly iterate through (potentially) every map key in order to find out whether or not a given class has been cached. On the other, relying on the default toString() implementation for every class in the factory isn’t necessarily a winning solution. As I said — each of these solutions comes with its own pitfalls. The toString() reliance is “fine” as long as anybody overridding it starts their String off with the name of the class and a colon! … 🤷‍♂️ Pick your poison, as they say.

Wrapping Up

I’m recently celebrating hitting six figures with regards to Joys Of Apex reads, and have been reflecting, a bit, on how unlikely a journey this little blog has been on in the past few years. If you’ve made it this far, know that I’m so grateful. I’m also publishing this only a few days after having had our guest post from Kai Tribble on CI/CD at GrubHub, so if you haven’t had a chance to read that article yet, make sure not to miss it. Till next time!

In the past three years, hundreds of thousands of you have come to read & enjoy the Joys Of Apex. Over that time period, I've remained staunchly opposed to advertising on the site, but I've made a Patreon account in the event that you'd like to show your support there. Know that the content here will always remain free. Thanks again for reading — see you next time!