Callable in Salesforce Winter 19
Introduction
The Winter 19 Salesforce release introduces the Callable interface which, according to the docs:
Enables developers to use a common interface to build loosely coupled integrations between Apex classes or triggers, even for code in separate packages.
upon reading this I spent some time scratching my head trying to figure out when I might use it. Once I stopped thinking in terms of I and started thinking in terms of we, specifically a number of distributed teams, it made a lot more sense.
Scenario
The example scenario in this post is based on two teams working on separate workstreams in a single org, the Core team and the Finance team. The Core team create functionality used across the entire org, for the Finance team and others.
The Central Service
The core team have created a Central Service interface, defining key functionality for all teams (try not to be too impressed by the creativity behind my shared action names):
public interface CentralServiceIF { Object action1(); Object action2(); }
and an associated implementation for those teams that don’t have specific additional requirements:
public class CentralServiceImpl implements CentralServiceIF { public Object action1() { return 'Interfaced Action 1 Result'; }
public Object action2() { return 'Interfaced Action 2 Result'; } }
The Finance Implementation
The Finance team have specific requirements around the Central Service, so they create their own implementation - in the real world this would likely delegate to the Central Service and enrich with finance data, but in this case it returns a slightly different string (artist at work eh?) :
public class CentralServiceFinanceImpl implements CentralServiceIF { public Object action1() { return 'Finance Action 1 Result'; }
public Object action2() { return 'Finance Action 2 Result'; } }
The New Method
Everything ticks along quite happily for a period of time, and then the Core team updates the interface to introduce a new function - the third action that everyone thought was the stuff of legend. The interface now looks like:
public interface CentralServiceIF { Object action1(); Object action2(); Object action3(); }
and the sample implementation:
public class CentralServiceImpl implements CentralServiceIF { public Object action1() { return 'Interfaced Action 1 Result'; }
public Object action2() { return 'Interfaced Action 2 Result'; }
public Object action3() { return 'Interfaced Action 3 Result'; } }
This all deploys okay, but when the finance team next trigger processing via their implementation, there’s something rotten in the state of the Central Service:
Line: 58, Column: 1 System.TypeException:
Class CentralServiceFinanceImpl must implement the method:
Object CentralServiceIF.action3()
Now obviously this was deployed to an integration sandbox, where all the code comes together to make sure it plays nicely, so the situation surfaces well away from production. However, if the Core team have updated the Central Service interface in response to an urgent request from another team, then the smooth operation of the workstreams has been disrupted. As the interface is a shared resource that is resistant to change - updating it requires coordination across all teams.
The Callable Implementations
Implementing the Core Central Service as a callable:
public class CentralService implements Callable { public Object call(String action, Map<String, Object> args) { switch on action { when 'action1' { return 'Callable Action 1 result'; } when 'action2' { return 'Callable Action 2 result'; } when else { return null; } } } }
and the Finance equivalent:
public class CentralServiceFinance implements Callable { public Object call(String action, Map<String, Object> args) { switch on action { when 'action1' { return 'Callable Action 1 result'; } when 'action2' { return 'Callable Action 2 result'; } when else { return null; } } } }
Now when a third method is required in the Core implementation, it’s just another entry in the switch statement:
switch on action { when 'action1' { return 'Callable Action 1 result'; } when 'action2' { return 'Callable Action 2 result'; } when 'action3' { return 'Callable Action 3 result'; } when else { return null; } }
while the Finance implementation can remain blissfully unaware of the new functionality until it is needed, or a task to provide support for it can be added into the Finance workstream.
Managed Packages
I can also see a lot of use cases for this if you have common code distributed via managed packages to a number of orgs. You can include new functions in a release of your package without requiring every installation to update their code to the latest interface version - as you can’t update a global interface once published, you have to shift everything to a new interface (typically using a V<release> naming convention), which may cause some churn across the codebase.
Conclusion
So is this something that I’ll be using on a regular basis? Probably not. In the project that I’m working on at the moment I can think of one place where this kind of loose coupling would be helpful, but it obviously makes the code more difficult to read and understand, especially if the customer’s team doesn’t have a lot of development expertise.
My Evil Co-Worker likes the idea of building a whole application around a single Callable class - everything else would be a thin facade that hands off to the single Call function. They claim it's a way to obfuscate code, but I think it's just to annoy everyone.
Related
This comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteNice explaination. I see some use cases, at same time need to think about all devs and mostly admins turning devs.
ReplyDeleteHello Bob,
ReplyDeleteThanks for this example. I was also wondering what this new feature could be used for.
It looks like the CentralService class was pasted twice. Also, the CentralService class might be missing the "CentralServiceIF" in the "Implements" section at the top.
Regards,
Zeki
Thanks Keir for your thoughts on this feature, I was scratching my head too.
ReplyDeleteThe code under "and the Finance equivalent:" seems wrong, it is for CentralService again.
I totally agree with your conclusion, the code starts to smell in callable.
My original thought is that we can build strings dynamically to call different functions, just like dynamical SOQL. But definitely it should be restricted in a small scope (e.g. inside one function only), instead of replacing interface which impacts multiple files.
Yeah you are right - I've changed the classname. I've also not used this in the wild yet, so it seems pretty niche to me.
Delete