7

In testing some integration hooks in our managed package, I was somewhat surprised by the behavior I saw today.

We have a managed package global class. It looks something like this:

global abstract class IntegrationHook {
    global IntegrationHook() {}
}

In a customer org, there is a class that looks something like this:

global class Integration extends dc.IntegrationHook {
    global Integration() {
        super();
    }
}

We added a constructor to the managed package superclass, like this:

global abstract class IntegrationHook {
    global IntegrationHook() {}
    global IntegrationHook(Integer i) {}
}

Deployed the new managed package to the customer org, then modified the local subclass:

global class Integration extends dc.IntegrationHook {
    global Integration() {
        super();
    }
    global Integration(Integer i) {
        super(i);
    }
}

This failed compilation, with the error "constructor is not visible: [dc.IntegrationHook](Integer)". I was puzzled at first, since I could see the superconstructor listed in the customer org's Apex Class as part of the exposed API.

To fix this, I had to modify the -meta.xml of my subclass from this:

<packageVersions>
    <majorNumber>1</majorNumber>
    <minorNumber>2498</minorNumber>
    <namespace>dc</namespace>
</packageVersions>

to this:

<packageVersions>
    <majorNumber>1</majorNumber>
    <minorNumber>2499</minorNumber>
    <namespace>dc</namespace>
</packageVersions>

This got me worried - are these version numbers pointing to actual old code, or is there some interface black magic going on where SFDC just remembers the old API and enforces it based on the version number?

Given how utterly restrictive the API evolution rules are for managed packages, and that they basically enforce 100% backwards compatibility, I do not understand why extending code needs to specify the package version it's talking to. The @Deprecated annotation doesn't seem enough justification for this to me?

The documentation has some not-very-technical mumbo jumbo:

When an existing subscriber installs a new package version, there is still only one instance of each component in the package, but the components can emulate older versions. For example, a subscriber may be using a managed package that contains an Apex class. If the publisher decides to deprecate a method in the Apex class and release a new package version, the subscriber still sees only one instance of the Apex class after installing the new version. However, this Apex class can still emulate the previous version for any code that references the deprecated method in the older version.

I think this means that it's interface-level only. Specifically is the implementation always going to be coming from the most recently installed package version? I would assume so but I'd like to be sure. Any help appreciated.

  • 1
    That's definitely not clear. I'd guess that it maybe stores the old version as well, perhaps the latest packaged version per API version. Hopefully someone on the inside can clear this up. – Matt Lacey Feb 28 '13 at 5:42
5

When you are developing code in an org with several packages, the platform tags via the metadata files the versions of those packages installed at the time against each metadata component (e.g. classes, triggers, pages, components, etc). Much like the platform API version. This can be seen via the Versions tab in the UI.

Interface Versioning: You are correct, by default this association enforces API versioning. Which I can see seems a little odd given the purely additive restrictions at present. To see new global class/methods/interfaces etc, as with the platform API, the developer needs to opt to associate with a later version themselves. Where as new classes or pages, as with the platform API, will attach to the latest version of your package and thus see the latest interface version. As you've identified "interface vesioning" = "metadata versionins" for your components.

Implementation Versioning: This is handled separately and implemented by your code. And does give rise to more justification for the above. The methods on the Version class allow your code to behave differently depending on what version of your package the caller was originally compiled against (determined via its metadata as above). And what expectations it has on the behaviour of your packaged code.

if (System.requestVersion() == new Version(1,0))
{
    // Do something
}
if ((System.requestVersion().major() == 1) 
     && (System.requestVersion().minor() > 0)
     && (System.requestVersion().minor() <=9))
{
    // Do something different for versions 1.1 to 1.9
}
else if (System.requestVersion().compareTo(new Version(2,0)) >= 0)
{
    // Do something completely different for versions 2.0 or greater
}

Summary: Interface Versioning vs Implementation Versioning vs Both. Typically most API's I've seen and those I prefer to create use interface versioning. These enable significant new behaviours explicitly via flags or new methods via additions to those interfaces. Allowing the implementation to shift regardless of the version of the interface so that old and new can benefit from big fixes. Or truly beneficial implementation improvements or enhancements regardless. This for me feels more open and clear to both the author and consumer of an API. That said there maybe a use case out there for this, it is probably a per case basis discussion. One thing to keep in mind is to monitor the complexity of implementation code if you do start seeding this kind of conditional logic through a complex code base.

| improve this answer | |
0

Extending a bit on Andrew's answer...

If you are using interfaces (as compared to classes), it's good practice not to add new methods to the interface, but instead to create an extension on that interface. For example:

global virtual interface myglobalinterface
{
    void methodA();
}

If you need to add a new method:

global virtual interface myglobalinterface2 extends myglobalinterface
{
    void methodB();
}

This allows you to control behavior based on the interface that is actually implemented - newer or updated code would typically implement the newer version of the interface.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.