Dependency Injection in Apex

The concepts of Inversion of Control (IoC) and Dependency Injection are well known in the traditional development world. I won’t delve into either concepts too deeply as others have done it in more detail and better than I can – Martin Fowler, James Shore, etc. The main idea behind these concepts is to remove explicit “dependencies” out of compiled code and “inject” those variables at run-time. Typically, this is used with services to be able to switch between different implementations using an external property file.

Scenario

In this scenario, we have a requirement to build a connection to a payment processor. This connector will vary on a customer by customer basis due to each customer using a different payment processor (could be CyberSource, Paypal, Stripe, Authorize.net, etc). We don’t want to change our code base for every single client. We need to encapsulate that functionality so we only have to make changes to one specific service. We can even take that a step further by getting rid of those dependencies and simply adding a new service implementation for each new client rather than changing any of the code we know is already stable. Let’s get started!

Interface

In this extremely simple scenario, all we need our service to do is process a payment. In order to ensure all of our service implementations, we will use an interface that our service will implement. By having an interface, our classes will know the exact methods that must be implemented. What those methods do is up to the implementation, but as far as the rest of the code base is concerned, it will know exactly how to work with those services.

[codegroup]
[java tab=”PaymentInterface”]
public interface PaymentInterface{
String processPayment();
}
[/java]
[/codegroup]

At this point, any classes that need to process a payment know they can call the processPayment() on a class that implements PaymentInterface. Before any of that can happen, we need some service implementations.

Service Implementations

Continuing with our example, we have two customers who want different connections to two separate payment processors, Paypal and Stripe. In order for the rest of the code base to be able to access these specific implementations, we will need to write classes that implement our interface.

[codegroup]
[java tab=”StripePaymentService”]
public class StripePaymentService implements PaymentInterface{
public String processPayment(){
// 1. Connect to payment processor
// 2. ???
// 3. Profit!
return ‘Stripe successfully processed your payment!’;
}
}
[/java]
[java tab=”PaypalPaymentService”]
public class PaypalPaymentService implements PaymentInterface{
public String processPayment(){
// 1. Connect to payment processor
// 2. ???
// 3. Profit!
return ‘Paypal successfully processed your payment!’;
}
}
[/java]
[/codegroup]

In the above code, you will notice that each service implements the PaymentInterface. This forces the service to include the processPayment() method.

Running the code

At this point, none of this is all that impressive. It is a pretty standard interface/class design. So, how do we go about running this code in a generic fashion? Well, we could do something like this:

[codegroup]
[java tab=”Run StripePaymentService”]
PaymentInterface payment = new StripePaymentService();
System.debug(payment.processPayment());

// Debug statement returns: Stripe successfully processed your payment!
[/java]
[java tab=”Run PaypalPaymentService”]
PaymentInterface payment = new PaypalPaymentService();
System.debug(payment.processPayment());

// Debug statement returns: Paypal successfully processed your payment!
[/java]
[/codegroup]

This still hasn’t gained us much since the code still needs to reference the actual class. So let’s get rid of that. To do this, we can change our code to look something like this:

[codegroup]
[java tab=”StripePaymentService – No Reference”]
PaymentInterface payment = (PaymentInterface)Type.forName(‘StripePaymentService’).newInstance();
System.debug(payment.processPayment());

// Debug statement returns: Stripe successfully processed your payment!
[/java]
[java tab=”PaypalPaymentService – No Reference”]
PaymentInterface payment = (PaymentInterface)Type.forName(‘PaypalPaymentService’).newInstance();
System.debug(payment.processPayment());

// Debug statement returns: Paypal successfully processed your payment!
[/java]
[/codegroup]

Now we are getting somewhere. By utilizing the Type class, we are able to instantiate each service with a String parameter. The Type class, part of a the System namespace, contains methods for getting the Apex type that corresponds to an Apex class and for instantiating new types. In this scenario, we will use the forName and the newInstance methods. The forName(String fullyQualifiedName) method:

Returns the type that corresponds to the specified fully qualified class name.

The fullyQualifiedName argument is the fully qualified name of the class to get the type of. The fully qualified class name contains the namespace name, if any.

The newInstance method:

Creates an instance of the current type and returns this new instance.

This method enables you to instantiate a Type that implements an interface and call its methods while letting someone else provide the methods’ implementation.

The final thing we do, after setting the forName to distinguish which class we wish to create and using the newInstance method to instantiate it, we have to cast it as the interface due to the fact that newInstance returns an sObject.

Unfortunately, we still have some changes to make. We still have two sets of code to instantiate both services. What we need to do now is remove the need for the String class name to be in the code at all. We can accomplish this using Custom Settings. You can use either a List or Hierarchical Custom Setting. There are use cases for both, but for my scenario, I am going to use a Hierarchical Custom Setting. I created a single custom setting that I could use for any of my services called InheritenceSettings__c. I then created a field on it called PaymentService__c. It looks like:

I can now store in text which implementation to use so my code can look like:

[java]
PaymentInterface payment = (PaymentInterface)Type.forName(InheritenceSettings__c.getInstance().PaymentService__c).newInstance();
System.debug(payment.processPayment());
[/java]

If my PaymentService__c value is set to StripePaymentService, then my debug statement would return Stripe successfully processed your payment!. If my PaymentService__c value is set to PaypalPaymentService, then my debug statement would return Paypal successfully processed your payment!. The beautiful thing about all of this is to change which implementation I am using, I never have to change any Apex at all. It completely makes that implementation interchangeable through a custom setting.

The final thing we are going to want to do is move this functionality out into it’s own service. We want to encapsulate that logic so it is only stored in a single place. This is typical of a service locator. In our code, we can create a class and add static methods on it to perform this functionality.

[java]
public class ServiceFactory{
public static PaymentInterface getPaymentInterface(){
return (PaymentInterface)Type.forName(InheritenceSettings__c.getInstance().PaymentService__c).newInstance();
}
}
[/java]

Our final set of code to run this now looks like:

[java]
PaymentInterface payment = ServiceFactory.getPaymentInterface();
System.debug(payment.processPayment());
[/java]

Conclusion

Using this methodology, we have been able to abstract the actual implementation from the code base and make the service dependent on a non-code related value. We have also been able to encapsulate the creation of that service to make it standard wherever it is used. Now while not all of this is the exact way a traditional programming language would do IoC, Dependency Injection, or Service Locator, it should work well in Apex.

Good luck and enjoy messing around with these design patterns!

Advertisement

Go to Smartblog Theme Options -> Ad Management to enter your ad code (300x250)

Comments are closed.