1

As part of my 2GP application (for AppExchange) I want to define single apex class as global to expose piece of logic to users. 

What are the implications of defining a global class and a global method?

I've seen https://ideas.salesforce.com/s/idea/a0B8W00000Gdh6xUAB/process-to-remove-global-classes-in-managed-packages by but I'm not sure if that only for 1GP.

I've also read https://developer.salesforce.com/docs/atlas.en-us.packagingGuide.meta/packagingGuide/packaging_managed_component_deletion.htm - but is that valid also for 2GP in any part?

Any best practices?

2 Answers 2

4

Note: The following is from 5+ years experience developing 1GPs and 2+ years with 2GPs as an ISV... it can, however, be viewed as opinionated.

According to the documentation on metadata deletion for 2GP it is still not possible to delete a global class or method.

Basically, once a global class or method is included in a 1GP or 2GP managed package you cannot (at this point) delete it. Since a signature change for a method is effectively the same as deleting the original method and adding a new one, you can't change the signature of a global method either.

You must only define things as global when essential, e.g.:

  1. Because the Salesforce feature requires it (e.g. custom REST API methods).
  2. Because you need the class and method(s) to be callable (or implementable) by code outside the package.

Be very careful about any inclusions of globals - the fewer you have the better.

For example, instead of creating custom REST APIs consider whether you can instead have a "command object" in your managed package where you attach automation against CRUD operations, so use of standard REST APIs is then enough. It is, after all, possible to add and remove custom objects and fields in 1GP and 2GP.

Another example is to adopt use of virtual global classes and methods to avoid problems with extending APIs - global interfaces become frozen once included in a package version so are very inflexible down the road.

Also consider using "parameter objects" in global methods in case you need to be able to add new parameters to methods later; you can always add new global attributes or properties to a global class (you just can't delete them). For these "parameter classes", consider using either a Builder pattern for construction (keep the constructors non-global and provide a global Builder for the class with relevant global setter and build methods) when you have required properties, or adopt a fluent interface for optional initialization of properties (with just a default constructor and chainable global setter methods).

BTW: always use apexdoc to document your APIs and use something like SfApexDoc to generate HTML documentation for your global APIs for sharing with your customers.

2
  • Thank you for you detailed answer, those are very good points. In my app - appexchange.salesforce.com/… - I wanted to give users / admins a way to run a script to get historical rates by running script like HistoricalRates.getHistoricalRates(2011, 01, 02); But looks like it's better to defined method with param like Map<String,Object> to have it more generic Aug 30, 2022 at 6:48
  • I personally would not use a Map, since this is not type safe and you cannot discover the parameters by looking at the generated documentation on the org. Instead have a global "parameters" class with global setters, properties or attributes for the various parameters you support. As I mentioned, you can add more globals to such a class if you need to. These can be nested global classes in your global API class.
    – Phil W
    Aug 30, 2022 at 6:56
1

The approach I've taken is to make use of the Callable interface provided by the platform and route through that. Its signature is open ended (by using very general types) but that is not a problem for simple operations.

So I write a class that looks like this:

global inherited sharing class DefaultCallable implements Callable {

     global Object call(String action, Map<String, Object> args) {
         if (action == 'action1') return action1(args);
         else if (action == 'action2') return action2(args);
         ...
     }

     private Object action1(Map<String, Object> args) {
         ...
     }

     private Object action2(Map<String, Object> args) {
         ...
     }
}

which as well as having the very open ended signature can be instantiated by other Apex code without introducing a compile-time dependency (as Callable is defined in the platform):

String name = 'ns.DefaultCallable';
Callable c = (Callable) Type.forName(name).newInstance();

Object result = c.call('action1', null);
// Cast result to the agreed type

So this provides one general purpose global access point.

9
  • I loath the Callable interface. It is not discoverable and has no type safety. It is a horrid fudge by Salesforce who should have provided full reflection capabilities instead...
    – Phil W
    Aug 29, 2022 at 21:48
  • BTW, since the call method belongs to the platform's Callable API you do not need to declare it as global in your implementation. The same applies for global Batchable implementation methods start, execute and finish (designed to be usable outside the package) etc.
    – Phil W
    Aug 29, 2022 at 21:53
  • Hi @PhilW, Well given what we have to work with, and for simple signatures, it has worked well for me. A pragmatic solution. Also doing more JavaScript stuff these days, the strong typing isn't as attractive for me as it used to be.
    – Keith C
    Aug 29, 2022 at 21:53
  • Fair point about the call method, but here the class does need to be global as the calling code needs to be able to instantiate it.
    – Keith C
    Aug 29, 2022 at 21:55
  • Within a single package you can certainly be looser with typing, since you will unit test it all yourself. But being loose across package boundaries is, IMHO, just making the implementers life harder and documenting your API way more difficult.
    – Phil W
    Aug 29, 2022 at 21:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .