Dependency Injection & The Factory Pattern
./img/joys-of-apex-thumbnail.png
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 Crud / CrudMock. As we head into the New Year, 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.
But let's take it slow. What does Dependency Injection look like? Let's take some simple Apex examples:
1linkpublic class AccountWrapper {
2link private final Account account;
3link
4link public AccountWrapper() {
5link this.account = [SELECT Id FROM Account LIMIT 1];
6link }
7link}
This is literally 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:
1linkpublic class AccountWrapper {
2link private final Account account;
3link
4link public AccountWrapper(Account account) {
5link this.account = account;
6link }
7link}
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.
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.
But how can we fix this problem? If we need to dynamically inject classes with other classes at runtime yet still have the flexibility to swap out those dependencies when testing, is there any way to do so?
Important preamble — I have found great success in using the method I am about to describe. If it doesn't float your Object-Oriented Boat, please accept my apologies.
The Factory Pattern offers a centralized place where your objects are constructed. Typically, the Factory ends up looking something like this:
1linkpublic abstract class Factory {
2link public static AccountWrapper getAccountWrapper() {
3link return new AccountWrapper(
4link //ruh roh, we still have this hard-coded dependency somewhere
5link //more on that later
6link [SELECT Id FROM Account LIMIT 1]
7link );
8link }
9link}
And whenever you need to initialize the AccountWrapper
:
1link//some code, somewhere, that needs the wrapper
2linkAccountWrapper wrapper = Factory.getAccountWrapper();
What are the benefits of this approach? Well, now our "client" code (so-called because it is the consumer of 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. 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 you need to create objects, instead of exponentially as your object's dependencies change and increase. Luckily ... there's a way.
Let's take our Factory example in Apex and improve it, slightly:
1linkpublic virtual class Factory {
2link public virtual Account getAccount() {
3link return [SELECT Id FROM Account LIMIT 1];
4link }
5link
6link public virtual AccountWrapper getAccountWrapper() {
7link return new AccountWrapper(this);
8link }
9link}
10link
11linkpublic class AccountWrapper {
12link private final Account account;
13link
14link public AccountWrapper(Factory factory) {
15link this.account = factory.getAccount();
16link }
17link}
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 Crud example:
1linkpublic virtual class Factory {
2link public ICrud Crud { get; private set; }
3link
4link private static Factory factory;
5link
6link @testVisible
7link protected Factory() {
8link this.Crud = new Crud();
9link }
10link
11link public static Factory getFactory() {
12link //production code can only initialize the factory through this method
13link if(factory == null) {
14link factory = new Factory();
15link }
16link
17link return factory;
18link }
19link
20link public static BusinessLogicThing getBusinessLogicThing() {
21link return new BusinessLogicThing(this);
22link }
23link
24link @testVisible
25link private Factory withMocks {
26link get {
27link this.Crud = new CrudMock();
28link return this;
29link }
30link }
31link}
32link
33link//your production code
34linkpublic class BusinessLogicThing {
35link private final ICrud crud;
36link
37link public BusinessLogicThing(Factory factory) {
38link this.crud = factory.Crud;
39link }
40link
41link public void handleBusinessLogic(List<Account> accountsNeedingBusinessLogicDone) {
42link this.updateAccountValues(accountsNeedingBusinessLogicDone);
43link this.crud.doUpdate(accountsNeedingBusinessLogicDone);
44link }
45link
46link private void updateAccountValues(List<Account> accounts) {
47link for(Account acc : accounts) {
48link //do business stuff here
49link //for now let's set some silly value
50link acc.Name = acc.Name + ' New';
51link }
52link }
53link}
54link
55link//your test code
56link@isTest
57linkprivate class BusinessLogicThing_Tests {
58link @isTest
59link static void It_Should_Update_Accounts_Correctly() {
60link //Given
61link String testString = 'Test';
62link Account fakeAccount = new Account(Name = testString);
63link
64link //When
65link BusinessLogicThing bizLogic = Factory.getFactory().withMocks.getBusinessLogicThing();
66link bizLogic.handleBusinessLogic(new List<Account>{ fakeAccount });
67link
68link //Then
69link Account updatedAccount = (Account) CrudMock.Updated.Accounts.singleOrDefault;
70link System.assertEquals(testString + ' New', updatedAccount.Name);
71link }
72link}
So what happened here? The Factory in Apex got initialized with the production level Crud wrapper - and in the production level code, we know that the Crud'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:
singleOrDefault
method will throw an exception if, as a result of the called code, more than one record exists in the list)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 Joy 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 Crud 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, and I'd like to give a short explanation:
Without the use of fancy tooling (and I am aware that there are some tools out there to do this, but so far the Salesforce sponsored tooling for VSCode is still kind of disappointing for non-DX orgs), onboarding new developers or consultants to an existing Salesforce.com project often involves hours and hours of head's-down code-digging, particularly to understand how objects are constructed. In an ideal world, where we would have access to niceties like Visual Studio's running count of how many times and where objects are being used, this would be less of an issue; but, considering that most developers are either clinging close to MavensMate/Sublime or VSCode and some combination of VSCode plugins, giving developers a one-stop-shop for where objects are initialized helps to familiarize them with the codebase and object interplay much faster.
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!
PS — I did some stress testing on the use of the CrudMock / Crud class I am recommending versus the FFLib Apex Mocks library which was developed by Andrew Fawcett, who worked on FinancialForce prior to working for Salesforce. FinancialForce's approach closely aligns with that of Mockito, one of the pre-eminent Java mocking solutions, and as such is widely accepted in the industry as the de-facto way to approach mocking within Apex. I will also be covering this in a future post, but for now if you are curious, check out the project on my Github for a sneak peek of the relative performance merits for each library. Cheers!
The original version of Dependency Injection & The Factory Pattern can be read on my blog.
Home Apex Logging Service Apex Object-Oriented Basics Batchable And Queueable Apex Building A Better Singleton Continuous Integration With SFDX Dependency Injection & Factory Pattern Enum Apex Class Gotchas Extendable Apis Formula Date Issues Future Methods, Callouts & Callbacks Idiomatic Salesforce Apex Introduction & Testing Philosophy Lazy Iterators Lightweight Trigger Handler LWC Composable Modal LWC Composable Pagination LWC Custom Lead Path Mocking DML React Versus Lightning Web Components Refactoring Tips & Tricks Replacing DLRS With Custom Rollup Repository Pattern setTimeout & Implementing Delays Sorting And Performance In Apex Test Driven Development Example Testing Custom Permissions Writing Performant Apex Tests