Versioning Apex Interfaces in managed packages

As documented in What are the pitfalls around Apex Interfaces in managed packages? a global Apex interface that has been published in a managed package can’t be modified. Trying to do so results in the compilation error:

Cannot add new abstract methods to interface or virtual or abstract classes:
[List of methods that have been added]

That makes sense, as any subscriber org that implemented the previous global interface wouldn’t currently be implementing the new methods.

However, given enough time there may be requirements that require changes to the interface as it is deployed. How can this be handled?

Answer

This is one possible approach based on extending the original interface.

Initial package definition:

global class FooProvider {
    //V1
    global interface CustomFooProviderV1 {
        string bar();
    }

    public static CustomFooProviderV1 getFooProviderInstance() {
        // Use a CMDT or Custom Setting to get the name of the implementing class
        string className = 'localFooProvider';
        // Use Type.forName and Type.newInstance() to create instance
        //...
        Type t = Type.forName(className);

        // Create an instance to confirm the type
        object testInstance = t.newInstance();

        return (CustomFooProviderV1)testInstance;
    }
}

Sometime later, add the new interface method:

global class FooProvider {
    //V1
    global interface CustomFooProviderV1 {
        string bar();
    }

    //V2
    global interface CustomFooProviderV2 extends CustomFooProviderV1 {
        string raiseTheBar(int height);
    }

    public static CustomFooProviderV2 getFooProviderInstance() {
        // Use a CMDT or Custom Setting to get the name of the implementing class
        string className = 'localFooProvider';
        // Use Type.forName and Type.newInstance() to create instance
        //...
        Type t = Type.forName(className);

        // Create an instance to confirm the type
        object testInstance = t.newInstance();
        if(testInstance instanceOf CustomFooProviderV2) {
           return testInstance;  
        }
        if(testInstance instanceOf CustomFooProviderV1) {
           return new FooUpgrader((CustomFooProviderV1)testInstance);
        }

        return null;   
    }

    public class FooUpgrader implements CustomFooProviderV2 {
        private CustomFooProviderV1 v1;
        public FooUpgrader(CustomFooProviderV1 v1Param) {
            v1 = v1Param;
        }
        public string bar() {
            return 'foobar';
        }
        public string raiseTheBar(int height) {
            throw new InterfaceVersionException('Method raiseTheBar is not supported with an implementation of the v1 FooProvider interface');
        }
    }
    public class InterfaceVersionException extends Exception {}
}

Other alternatives include:

  1. create an entirely new interface that just covers the new methods.
  2. Have different versions of getFooProviderInstance that target specific API versions. Then the wrapper Upgrader class wouldn’t be required and the caller wouldn’t need to handle the potential exceptions.

Attribution
Source : Link , Question Author : Daniel Ballinger , Answer Author : Daniel Ballinger

Leave a Comment