adapter

Adapting Plugin APIs

Managed package code can be written to allow subscribers to plug in their own implementation of a global interface. Typically this is done with the managed package providing a custom setting that allows the subscriber to specify a concrete implementation of an interface. The managed package code then creates an instance using the Type.forName method. Once a global interface is included in a managed package (i.e., published) it can no longer have methods added to it in subsequent versions, yet developers of the managed package may strongly desire to do something to that effect. This article explores a pattern that can be used by the managed package code to decouple it from the evolving plugin APIs. A simple example that uses a PaymentProcessor interface is used to illustrate the main points.

On the initial release there is a single interface, PaymentProcessor, with a single method.  Subscribers can implement and specify their concrete implementation in a custom setting, PaymentProcessor__c.Service__c.

The global interface

It may be used in the managed package as follows.  (The details of the Type.forName are hidden in the factory.)

Once installed in the subscriber org, the subscriber may create their own implementation.

Everything is fine, until in the next release requires refunds, cancellations, and authorizations to be implemented. One possibility is to create a new plugin custom setting and use that in addition to the existing custom setting. Another possibility is to keep the one and only custom setting and allow the subscriber to update their concrete implementation to implement the new interface. That latter is explored more in detail below, but the code could be modified to accommodate the former.

A new global interface

Here the new interface just extends the previous one, but it doesn’t have to be done that way. It could be separate and left up to the implementing classes to implement both interfaces.  One benefit of doing it this way is to make it clear to all new subscribers that they should be implementing the PaymentProcessor2 interface.

A new class is introduced to act as a middle-man between the controller and the plugin. This is an implementation of the object adapter pattern.

The factory is then updated to return a PaymentProcessorAdapter.

The DefaultPaymentProcessor2 could be any number of things. It could be a Null object pattern implementation. It could implement real behavior, if the processing it did made sense universally. It could throw UnsupportedOperationExceptions. The proc2 could be set to null, and the Adapter could handle that situation, even. It depends on whatever makes the most sense for the plugin.

The controller would change to reference the adapter.

The subscriber implementation would need to change to implement the new interface.

Additional Benefits

In the above example the adapter class implements its own method for each plugin method (i.e., a one-to-one). It does not have to, though. The methods on the adapter could be completely different and tailored to how they would make sense in the internal / managed package code. Other methods could be added to the adapter to give the calling code the ability to test if certain functionality is available.

Alternative – Add to Parameters and Return Types

In the adapter example the makePayment method took a single PaymentRequest object and returned a single PaymentResponse object. Depending on the similarity of the functions, it could be a better solution to add properties to the parameter and return objects (e.g., a transactionType to denote payment, refund, cancel, or authorize). This approach has the benefit of not requiring another interface, but makes it easier to not implement the new methods.

Although the method signature wasn’t changed, the preconditions and postconditions did change to be more complex. Be careful about not breaking calling code or implementing code. Refer to this table on general rules for contract compatibility (via Eclipse).

Method preconditions Strengthen Breaks compatibility for callers Contract compatible for implementors
Weaken Contract compatible for callers Breaks compatibility for implementors
Method postconditions Strengthen Contract compatible for callers Breaks compatibility for implementors
Weaken Breaks compatibility for callers Contract compatible for implementors
Field invariants Strengthen Contract compatible for getters Breaks compatibility for setters
Weaken Breaks compatibility for getters Contract compatible for setters

Conclusion

This article described a way to use the object adapter pattern with plugin classes to facilitate changing plugin APIs. The pattern is general and pieces of it can be easily varied, depending on the needs of the developer and/or codebase.

Leave a Reply

Your email address will not be published. Required fields are marked *