Dependencies: good or bad?
When it comes to code dependencies, you will often see this example:
public ClassA { } public Class B { private ClassA service; }
As you can see, ClassB needs ClassA to do its job, in other words, Class B is dependent on ClassA. This is a dependency.
But it’s a pretty boring example. Salesforce Developers or Administrators can introduce dependencies in innumerable ways.
Let’s imagine you have installed a managed package in your Salesforce organisation (if you’re not familiar with Salesforce Managed Packages, just think about a third-party service). The package provides CTI (telephony) integration, so your Salesforce Users can now call customers directly from Salesforce. And you don’t have to build everything from scratch! The only thing you need to do is a package configuration. You’re adding packaged CTI components to Salesforce Layouts or FlexiPages, and a Phone button to your Salesforce Flows. Maybe you’re also referencing Package Apex Classes in your code if you want to build backend-side integration.
After several months (or maybe years) your manager asks you to remove the package and install another from AppExchange. Why? Well, it doesn’t really matter. Probably, a severe security vulnerability was found in the old package. Or business decided to use a cheaper package. Or probably the new package supports some new cool features. You don’t control changes in third parties your Salesforce org depends on. And you will always face this because modern software will always have dependencies (nobody wants to reinvent the wheel).
Now let’s imagine you have multiple Salesforce Managed Packages installed in your org. At some point, you can find yourself spending more time fixing dependency issues rather than implementing new features – in this case, your project is in what is commonly referred to as “dependency hell”.
Dependencies can complicate code maintenance – the more dependencies your Salesforce code has, the more you have to manage. They can bring security or performance issues to your team, especially if managed packages or third-party libraries you depend on are outdated. Also, they decrease code reuse affecting development speed, code quality, code readability, etc.
Well, are dependencies evil? Definitely not. Many Salesforce Managed Packages or third-party libraries and services exist to solve common problems: telephony, logging, analytics, connection to popular services, etc. You can easily acquire them and integrate them into your codebase. And then, you can concentrate on code that solves uncommon business-specific problems.
That’s why you’ll need to find a balance in modern software development. Instead of removing dependencies (unless it is unused), you should have proper dependency management. How can you manage dependencies? For example, you can inject them.
Dependency injection
Do you remember SOLID principles by Uncle Bob? Today we need just the last one: D, which stands for “Dependency Inversion Principle” (that’s right, Inversion, not Injection). It states that entities must depend on abstractions, not on concretions. What is “entity” from this statement? It can be Apex Class, Lightning Web Component, Aura Component or even Visualforce Component. An entity should concentrate on fulfilling its responsibilities and not on creating resources required to fulfil those responsibilities. Dependency Inversion is a principle that declares what developers should do, but it does not describe how developers should implement it. And that’s where Dependency Injection comes into play.
Dependency Injection is a technique that allows developers to decouple the creation of the usage of a resource. A resource is an object your entity (for example, Apex Class) depends on. That enables developers to replace dependencies without changing the entity that uses them. Dependency Injection is not a library, technology or framework. It’s just advice for developers to handle dependencies somewhere other than the dependent entity. Many well-known frameworks implement the DI technique in different programming languages: Spring (Java), Unity (.NET), etc.
ForceDI framework
But are there any modern Dependency Injection frameworks for Salesforce? Of course, you won’t see as many options as in Java. Some Salesforce companies are implementing their custom DI solutions. For example, you can check these Salesforce Blog posts:
These articles describe how developers can replace “Hard (compile-time) Dependencies” with “Soft (runtime) Dependencies”. It is a pretty straightforward pattern you can integrate into your Apex codebase.
But if you’d like to manage more sophisticated dependencies in Apex code and Frontend components, you can consider utilising the open-source ForceDI library built by Andrew Fawcett. With this library, you can manage your Apex, Visualforce, Aura, LWC and Flow dependencies. Please check the original post about the library or its GitHub repo if you want to learn more. The following sections describe how you can integrate ForceDI library into your Salesforce codebase.
ForceDI for Apex
Let’s return to our example about the telephony package. You would like to build an Apex Service class that pulls statistics about monthly calls from your CTI system.
/* * Your Apex Class that pulls monthly telephony statistics * from your CTI system. The class utilises managed Apex class * from the telephony package. */ public with sharing class CallStatistics { public telephony__Statistics getStatistics(Date fromDate, Date toDate) { // some preparations telephony.StatisticsService service = new telephony.StatisticsService(); return service.retrieve(fromDate, toDate); } }
Your class CallDetails
has method getStatistic(Date fromDate, Date toDate).
The method encapsulates a call to the managed class StatisticsService
from the telephony package. Don’t forget that you’re referencing managed code, so you must prepend package namespace: telephony.StatisticsService.
So now your class CallDetails is tightly coupled with StatisticService, which means:
- You cannot deploy these classes separately. You must always install the telephony package even if you want to add unrelated changes.
- You cannot put these classes into different force-app packages.
- You must change your client Apex class If you change your telephony package.
- You don’t have configuration flexibility at runtime.
- Your code probably violates GRASP principles, especially Low Coupling principle.
Your manager asks you to remove tight dependencies between the code and third parties. With ForceDI you can do this:
public with sharing class CallStatistics { public telephony__Statistics getStatistics(Date fromDate, Date toDate) { // some preparations CallStatistic service = (CallStatistic) di_Injector.getInstance(CallStatistic.class); return service.retrieve(fromDate, toDate); } }
Let’s explain how it works. We can start with UML diagram:
In this schema, you can see some new classes:
CallStatistics
interface. Your client classCallDetails
will depend on an interface rather than concrete classes. We can call it Soft Dependency.TelephonyPackageStatistics
class that implementsCallStatistics
. This class is tightly coupled with managed classStatisticsService
, and that’s why your code should not depend onTelephonyPackageStatistics
.di_Injector
class from ForceDI library, which performs actual magic behind the scenes.
How does di_Injector class know which concrete Apex Class it should instantiate when we call di_Injector.getInstance(CallStatistic.class)
? Well, it takes it from the Binding Configuration, which is stored in Custom Metadata Type (actually, if you modify ForceDI library you can choose any place you like).
di_Injector
class takes the target Apex class name from the configuration in runtime and creates its instance. Suppose your manager asks you to replace the package with another package newTelephony
. In that case, you just need to provide class NewTelephonyStatistics
(you can name it as you wish) and change the binding configuration. That’s it! You don’t have to change your client code. Also, you can deploy your code even if TelephonyPackageStatistics
or NewTelephonyStatistics
does not exist in your Salesforce org.
You can put CallDetails
and TelephonyPackageStatistics
classes into different force-app directories and deploy them in different packages.
This was a very basic example of how you could manage Apex Code dependencies. But what if your Lightning code also has tight dependencies? ForceDI library can handle them as well.
ForceDI for Lightning
Note: this example is applicable for Lightning Web Components as well, although you have to wrap them into parent Aura components since LWCs do not yet support an alternative for $A.createComponent.
Imagine you have your custom Aura Component PhonePanel which allows Salesforce Users to make calls to your customers directly from Lightning Interface. You can use this component in Salesforce FlexiPages, UtilityBar or Quick Actions.
Like in the Apex example, your Aura component references managed Aura or LWC component Phone from telephony package (when you’re referencing managed Aura components, make sure you prepend package namespace, for example telephony:Dialpad
).
<aura:component implements="flexipage:availableForAllPageTypes"> <telephony:Dialpad callerId="{!v.callerId}" loggingEnabled="{!v.loggingEnabled}"/> </aura:component>
In the example above, the client Aura component additionally passed two input parameters to the managed component.
With ForceDI your code will reference the di_injector Aura component and provide a binding name (in our example the binding name is PhoneComponent
).
<aura:component implements="flexipage:availableForAllPageTypes"> <c:di_injector bindingName="PhoneComponent"> <c:di_injectorAttribute name="callerId" value="{!v.callerId}"/> <c:di_injectorAttribute name="loggingEnabled" value="{!v.loggingEnabled}"/> </c:di_injector> </aura:component>
The principle behind di_injector
Aura component is similar to what we had in the Apex example: the component finds target Lightning component in Binding Configuration and then instantiates it at runtime.
And what about other dependencies?
As I mentioned, Salesforce dependencies are not limited to Apex and Lightning. You can have dependencies between different components.
- Apex and Lightning (your custom Lightning component uses Apex controller from a managed package)
- Metadata and Lightning (your FlexiPage can include references to managed Aura or LWC)
- Visualforce page and Visualforce component (your VF page includes managed VF component)
- Flow and Lightning (your add managed Lightning component to your Salesforce Flow)
- Flow and Apex (flow calls external Apex class)
- Etc.
Additionally, you can have internal dependencies. Previously we reviewed external dependencies (your custom code references components from third-party packages you don’t control). But with DI, you can also remove strong internal dependencies within your codebase and decompose large and complex Salesforce orgs into different low coupled modules (DX projects).
Conclusion
Hopefully, this blog helped you understand how to manage dependencies in your Salesforce environment. Dependency Injection is not the only way to manage dependencies (patterns such as Observer
or Strategy
still exist), but it’s definitely one of the most powerful principles that help you to maintain your codebase and focus on important problems rather than trying to resolve spaghetti dependencies and fixing problems.