As I implied in a recent question, I know just enough Apex to be dangerous when trying to implement more complex concepts. (And by "complex" I mean "complex to me" because so many here are so complex...
Dynamically Determine Calling Context?
Design Patterns - Singleton applied to Apex w/o describe call limits
...that it is hard for me to comprehend some things...yet.)
I also have limited knowledge/understanding of various patterns (e.g., the Singleton discussed in the question linked above). (Patterns are actually my intended next focus on Trailhead.) So I have tried to look over some things, including (but not limited to):
Best practice with big classes Apex
https://www.jamessimone.net/blog/joys-of-apex/organizing-invocable-and-static-code/ (I did not follow most of this one, tbh.)
Sometimes I see mention of having classes that are too big; other times mention of having too many classes. I do know enough and have enough life experience to know that the pendulum can swing too far one way at times, and that often you must delve into the fuzzy logic between each end in order to determine what is good/better/best/correct in any given circumstance.
(My main takeaway from a semester of an undergraduate senior-level Intro to Operating Systems course a few years ago was: "It depends.")
What I am (thinking that I) want to do is have one utility class that houses a number of invocable methods which can be called from Flows.
Reasoning
If we have a handful of useful utility methods which we want accessible to Flows, we have to create an equal number of classes each with an InvocableMethod
. My gut tells me that this would be more frustrating to maintain.
Having separate classes would make it easier to find the method you want in the search box in the Apex Action Flow element (as long as your class was named well and your method label was descriptive). But if wanting to call something from a non-Flow context, having separate classes might make it harder to determine whether you already have a utility method to do what you need.
So it makes sense to me to try to implement what I'm thinking, but please tell me how I am mistaken.
I can't just create a class full of invocable methods, since only one InvocableMethod
is allowed per class. So I would need to go a different route.
I don't think that I can use a Callable
interface because A) that requires (String, Map<String, Object>)
input parameters and invocable methods can only take at most one input parameter, and B) I don't believe we can yet create/use an actual Map in a Flow.
So I have essentially created my own version of a Callable
implementation. Here is a proof of concept which is working so far:
public class UtilitiesInvocable {
public class Requests {
@InvocableVariable(label='Method Name' required=true)
public String methodName;
@InvocableVariable(label='Input String')
public String inputString;
@InvocableVariable(label='Delimiter')
public String delimiter;
}
public class Results {
@InvocableVariable
public String outputString;
@InvocableVariable
public List<String> outputStringList;
}
@InvocableMethod(label='Methods: getOrgBaseUrl, splitString')
public static List<Results> methodParser(List<Requests> requestList) {
List<Results> responseWrapper = new List<Results>();
for (Requests req : requestList) {
Results response = new Results();
switch on req.methodName {
when 'getOrgBaseUrl' {
response.outputString = getOrgBaseUrl();
}
when 'splitString' {
response.outputStringList = splitString(req.delimiter, req.inputString);
}
when else {
response.outputString = 'Method not found: ' + req.methodName;
}
}
responseWrapper.add(response);
}
return responseWrapper;
}
public static String getOrgBaseUrl() {
String urlString = System.Url.getOrgDomainUrl().toExternalForm();
return urlString;
}
public static List<String> splitString(String delimiter, String stringToSplit) {
List<String> stringList = stringToSplit.split(delimiter);
return stringList;
}
}
At first I tested with, and intended for, the individual methods to all return Results
...
public static Results getOrgBaseUrl () {
Results response = new Results();
response.outputString = System.Url.getOrgDomainUrl().toExternalForm();
return response;
}
...figuring that would be more uniform and easy to deal with within the methodParser
switch
statement. It worked, but after further thought, doing that would make calling these methods directly from a non-Flow context much more cumbersome because a UtilitiesInvocable.Results
object would have to be instantiated before calling the method.
Further Thoughts, Considerations, Comments
If the only input that is ever "allowed" to be required is the
methodName
, then the class should easily be expanded as needed without breaking existing usage.If the number of inputs grows very large due to having multiple methods and/or complex methods that have a large number of parameters, it could get cumbersome or confusing in a Flow because the Apex Action element lists EVERY available input variable.
If the variables were named generically enough in the class, some could be reused among methods, but it could still get "busy" and their genericity might add to the confusion.
This class would definitely need to be very well documented - especially for Admins or other users who don't hang out in the developer world.
In our use case, I suspect we would have at most 5 to 10 small utility methods inside of this class.
If the available methods are listed in the
label
of the invocable method annotation, they are searchable/visible in the search box of the Apex Action element:If the number of methods increases greatly, it is not pretty, but it still functions at least out to this many characters:
Additional question if you think that this worth attempting to implement
- Instead of returning an
outputString
that states the requested method was not found, would it be better so something else (e.g., throw a custom exception)?
getOrgUrl
invocable method inside its own class, but just "redirect" the call from the Flow to a different non-invocable utility class which includes the "real"getOrgUrl
method, which returns results to the invocable, which returns to the Flow? Then similarly have a separate class +splitString
invocable method that calls the "real"splitString
method in the utility class?