Dependency Injection & The Factory Pattern
Posted: December 31, 2019

Dependency Injection & The Factory Pattern

Table of Contents:

  • Intro to Dependency Injection in Apex
  • The Problems with Dependency Injection
  • Using the Factory Pattern In Apex
  • The Factory Pattern & Constructor Injection
  • Summing up Dependency Injection & The Factory Pattern In Apex
  • DI Factory Addendum

Welcome back to another episode in the continuing Joys Of Apex discussion. In my last post, I covered how to wrap your SFDC DML operations using DML / DMLMock. Now, let’s talk about Dependency Injection (DI) - popularly used to simplify object creation, allow for polymorphic construction of objects, and easily stub out object dependencies while writing tests.

Intro to Dependency Injection in Apex

But let’s take it slow. What does Dependency Injection look like? Let’s take some simple Apex examples:

public class AccountWrapper {
  private final Account account;

  public AccountWrapper() {
    this.account = [SELECT Id FROM Account LIMIT 1];
  }
}

This is the simplest wrapper object possible. But if you ever want to change the reference to the Account being stored in the constructor, or mock it for testing the AccountWrapper’s functionality, you’re going to need to go with another approach — injecting the Account SObject via the constructor:

public class AccountWrapper {
  private final Account account;

  public AccountWrapper(Account account) {
    this.account = account;
  }
}

Now, it becomes the object responsible for initializing the AccountWrapper to pass in the correct Account reference; this also means that you can easily create a dummy account for your tests when you don’t require a specific account to test your wrapper’s functionality.

The Problems with Dependency Injection

Dependency injection thus becomes your one-stop shop, especially when making sensible unit tests, for passing in only the objects your class needs in order to test what you’re testing. Dependencies that aren’t used on the code path you’re testing can be omitted, or mocked through a shared interface or extended mock object. But it wouldn’t be Apex without a few roadblocks thrown your way care of Salesforce:

  • because Apex is compiled by pushing your local classes up to the cloud, you can run into issues where refactoring your existing objects can be made difficult when changing the signature of an object’s constructor. Anybody who’s ever waded through the dreaded Dependent Class Is Out Of Alignment error when deploying your Apex classes knows that if your objects are all being constructed via the new keyword in many different places, correctly refactoring your existing initialization statements to properly match your constructor signature can be … difficult, or at the very least, time-consuming. Time is precious. This isn’t always a problem if you can deploy all classes in an org, or all of the classes that have changed in one go (the default for scratch-org based development), but it can be a very time-consuming process in larger orgs where the change you’re making is rippling out to dozens of other classes
  • managed packages, and to some extent, Second Generation Packages (2GP) have to plan constructor-based changes more carefully than other Salesforce development teams (for global visibility classes). This may include retaining older constructors that are no longer in-use within a class to prevent a breaking change when deploying package upgrades. While managed packages can make use of the @Deprecated class/method decorator for such cases, it still increases tech debt to have this unused code lying around.

Practically, this means that it’s very easy for new objects to delegate their own construction by way of the Factory pattern (more on this in a second). For legacy code, some iteration may be involved to get everything right. When my original team moved to using the Factory pattern, we did it all in one go. The pull request was hundreds of files. Sometimes that sort of “big bang” approach is preferred; other times it may make more sense to slowly migrate existing objects with dependencies to use the Factory pattern over time. Let’s dive in and show you how!

Using the Factory Pattern In Apex

The Factory Pattern offers a centralized place where your objects are constructed. Typically, the Factory ends up looking something like this:

public class Factory {
  public static AccountWrapper getAccountWrapper() {
    return new AccountWrapper(
      // ruh roh, we still have this hard-coded dependency somewhere
      // more on that later
      [SELECT Id FROM Account LIMIT 1]
    );
  }
}

And whenever you need to initialize the AccountWrapper:

// some code, somewhere, that needs the wrapper
AccountWrapper wrapper = Factory.getAccountWrapper();

What are the benefits of this approach? Well, now our “client” code (so-called because it is the consumer of the initialized AccountWrappers) doesn’t need to know how the object is constructed; it’s agnostic of the wrapper’s dependencies, which can change without having to refactor all of the places using the object. But things still aren’t ideal. Initializing the objects within the Factory has brought about the centralization of how your objects are being constructed … but the Factory itself now runs the risk of becoming extremely bloated. Worse, it now needs to know how to construct each object — that’s really not ideal because it means the Factory class knows too much about each object. It breaks encapsulation, in other words.

We’d like for the Factory to be responsible for object initialization without having to know exactly what each object needs. That way, it expands in size only so much as your need to create objects, instead of exponentially as your object’s dependencies change and increase. Luckily … there’s a way.

The Factory Pattern & Constructor Injection

Let’s take our Factory example in Apex and improve it, slightly:

public virtual class Factory {
  public virtual Account getAccount() {
    return [SELECT Id FROM Account LIMIT 1];
  }

  public virtual AccountWrapper getAccountWrapper() {
    return new AccountWrapper(this);
  }
}

public class AccountWrapper {
  private final Account account;

  public AccountWrapper(Factory factory) {
    this.account = factory.getAccount();
  }
}

Wowza. The Factory stays slim, expanding only as you add new objects to your codebase; the responsibility for construction still lies with the object, and it always has access to the dependencies it needs! That’s pretty cool. You can also stub out the getAccount method, should you need to fake your account dependency in tests. Even nicer, if the getAccount() method is one used frequently in tests, you can also centralize your overrides in a shared test file.

But let’s bring it back to our DML example:

public virtual class Factory {
  public IDML DML {
    get;
    private set;
  }

  private static Factory factory;

  protected Factory() {
    this.DML = new DML();
  }

  public static Factory getFactory() {
    // production code can only initialize the factory through this method
    if(factory == null) {
      factory = new Factory();
    }

    return factory;
  }

  public virtual BusinessLogicThing getBusinessLogicThing() {
    return new BusinessLogicThing(this);
  }

  @TestVisible
  private Factory withMocks {
    get {
      this.DML = new DMLMock();
      return this;
    }
  }
}

// your production code
public class BusinessLogicThing {
  private final IDML dml;

  public BusinessLogicThing(Factory factory) {
    this.dml = factory.dml;
  }

  public void handleBusinessLogic(List<Account> accountsNeedingBusinessLogicDone) {
    this.updateAccountValues(accountsNeedingBusinessLogicDone);
    this.dml.doUpdate(accountsNeedingBusinessLogicDone);
  }

  private void updateAccountValues(List<Account> accounts) {
    for (Account acc : accounts) {
      // do business stuff here
      // for now let's set some silly value
      acc.Name = acc.Name + ' New';
    }
  }
}

// your test code
@IsTest
private class BusinessLogicThing_Tests {
  @IsTest
  static void It_Should_Update_Accounts_Correctly() {
    // Given
    String testString = 'Test';
    Account fakeAccount = new Account(Name = testString);

    // When
    BusinessLogicThing bizLogic = Factory.getFactory().withMocks.getBusinessLogicThing();
    bizLogic.handleBusinessLogic(new List<Account>{ fakeAccount });

    // Then
    Account updatedAccount = (Account) DMLMock.Updated.Accounts.singleOrDefault;
    System.assertEquals(testString + ' New', updatedAccount.Name);
  }
}

So what happened here? The Factory in Apex got initialized with the production level DML wrapper - and in the production level code, we know that the DML’s doUpdate method is going to correctly update Accounts run through the BusinessLogicThing. But in the tests, we can completely avoid having to hit the database and the subsequent requerying to verify that the Account Names have been updated accordingly. We can simply go to our mock and verify that:

  • the Account has been put into the correct list
  • there’s only one of them (the singleOrDefault method will throw an exception if, as a result of the called code, more than one record exists in the list)
  • the Name property has been updated, as we expect

That’s the power of the Factory Pattern when used with Dependency Injection. Typically, test slowness is the result of the Salesforce database being hit. So far in the Joys Of Apex, we’ve covered one possible approach to hitting the database — through DML operations like record creation, upserting, updating, deleting, or undeleting.

There is another side to test slowness, though — accessing the database through querying. In an upcoming episode in this ongoing series, I’ll cover how you can use the Repository Pattern similarly to our DML implementation to hot-swap out querying the database while in tests. And once we’ve covered the Repository … that’s it. There have been some voicing the opinion that this code is a lot of boilerplate; on the contrary, I find that organizations making use of these files typically save on lines of code, test complexity, and developer overhead. That last point is particularly important, as it helps people onboarding to read code more easily (something that will continue to be a theme in this blog). Whenever we can reduce the complexity of the classes we’re using, it’s a big win when it comes to understanding complex business logic — this also happens to frequently reduce the amount of time it takes to make changes, which is always a win too.

Let’s look at how complex object hierarchies can end up benefitting from the use of the Factory:

public virtual class Factory {
  // ... top is unchanged
  public virtual BusinessLogicThing getBusinessLogicThing() {
    return new BusinessLogicThing(this);
  }

  public virtual RelatedContactUpdater getRelatedContactUpdater() {
    return new RelatedContactUpdater(this);
  }
}

// and then ...
public class RelatedContactUpdater {
  private final IDML dml;
  private final BusinessLogicThing bizLogic;

  public RelatedContactUpdater(Factory factory) {
    this.bizLogic = factory.getBusinessLogicThing();
    this.dml = dml;
  }

  public void process(List<Account> accounts) {
    this.bizLogic.handleBusinessLogic(accounts);
    // now that our dependency has done its work
    // we'll also update the related contacts
    Map<Id, Account> idToAccounts = new Map<Id, Account>(accounts);
    // this bare SOQL query ties us to the database.
    // look for the Repository Pattern post to see how
    // to improve this!
    List<Contact> relatedContacts = [
      SELECT Id, LastName
      FROM Contact
      WHERE AccountId = :idToAccounts.keySet()
      // pre-order the contacts by AccountId to reduce
      // chance for record-locking on update
      ORDER BY AccountId
    ];

    List<Contact> contactsToUpdate = new List<Contact>();
    for (Contact con : relatedContacts) {
      Account relatedAccount = idToAccounts.get(con.AccountId);
      // note that this is an implicit coupling between "BusinessLogicThing"
      // and, now, our RelatedContactUpdater. In a real-world example,
      // it would be more likely for a checkbox (Boolean) field to be set on some
      // records, and here we would be testing whether or not that Boolean was true
      if (relatedAccount.Name.endsWith(' New')) {
        // update the contact in some way here
        contactsToUpdate.add(con);
      }
    }
    this.dml.doUpdate(contactsToUpdate);
  }
}

In a more complicated example, we might also need to make BusinessLogicThing virtual (or have an interface with handleBusinessLogic declared on it) if we want to stub it when testing RelatedContactUpdater:

public virtual class BusinessLogicThing {
  public virtual void handleBusinessLogic(List<Account> accountsNeedingBusinessLogicDone) {
    // etc ...
  }
}

But since we are only looking at mocking DML at the moment, RelatedContactUpdater can already take advantage of the existing withMocks call in the factory:

@IsTest
private class RelatedContactUpdaterTests {

  @IsTest
  static void no_contacts_are_updated_without_accounts() {
    Factory.getFactory().withMocks.getRelatedContactUpdater().process(new List<Account>());

    System.assertEquals(true, DMLMock.Updated.Contacts.isEmpty());
  }

  @IsTest
  static void it_updates_related_contacts_successfully() {
    Account acc = new Account(Name = RelatedContactUpdaterTests.class.getName());
    insert acc;
    List<Contact> contacts = new List<Contact>{
      new Contact(LastName = 'One', AccountId = acc.Id),
      new Contact(LastName = 'Two'), AccountId = acc.Id
    };
    insert contacts;

    Factory.getFactory().withMocks.getRelatedContactUpdater().process(new List<Account>());

    System.assertEquals(2, DMLMock.Updated.Contacts.size());
    // etc ... asserting on whatever fields were updated in RelatedContactUpdater
  }
}

Summing up Dependency Injection & The Factory Pattern In Apex

Our tests are incredibly succinct. And, because we’re mocking almost all of the DML in our tests, they’re also quite fast. Our Factory has allowed us to easily construct the RelatedContactUpdater; if its responsibilities expand, we can easily add dependencies to it from the factory without having to change its constructor, and without the factory having to know anything about how the object is being constructed. As factories grow in size, they also don’t need to be “one size fits all” — you can have factories that are smaller and serve up small parts of your business domain. One of my old teams had several different factories in play, ranging from:

  • HandlerFactory — only called within Triggers, responsible for setting up trigger handlers. Stored an instance of …
  • BaseFactory — responsible for most business-logic class construction. Stored an instance of …
  • RepoFactory — responsible for constructing Repository classes, as well as the eventual “home” of the DML class. This is the subject of the next post!

I have a very small sample repository which was created after appearing on the SalesforceWay talking about DML mocking which walks through all of the DML / Factory / Repository-level code. You may find that repository an illuminating read! It’s been used as the starting point for many an org’s switch to taking advantage of the factory pattern.

Here’s to hoping you enjoyed this episode of The Joys Of Apex. More to come in 2020 — for now, wishing you all a Happy New Year!


DI Factory Addendum

Zero argument constructors are ideal (whenever possible) in Apex because they allow you to opt-in to the use of Type.forName(name).newInstance(), which is as close to generics as Apex currently is going to get. While what we’ve shown here uses single-argument constructors to allow the Factory object as the only instantiating factor, you could just as easily create zero-argument constructors and set the Factory instance using the getFactory method shown off here. I show that off explicitly in Organizing Invocable & Static Code! Here’s an example, using the BusinessLogicThing class from above:

public class BusinessLogicThing {
  private final IDML dml;

  // now this object works with Type.forName('BusinessLogicThing').newInstance()
  // which is sometimes necessary with more dynamic apex!
  public BusinessLogicThing() {
    this(Factory.getFactory());
  }

  public BusinessLogicThing(Factory factory) {
    this.dml = factory.dml;
  }
}

More posts on the subject of the ever-useful Type.forName method can be found here:

Lastly, “Factory”-type objects don’t always have to be in their own class. They’re of particular use as methods returning instances of an abstract class or interface when you use the replace conditional with polymorphism pattern, for example. That’s a really fun post to read through for another way factories can help you keep your code organized and abstracted at the right level! For more reading on how you can use the Factory, make sure to not miss out on reading You Need a Strongly Typed Query Builder, as well as Bulk Lead Conversion!

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!