How "Executes an activity by its type name" works in DSL example

Hello,

I am following the DSL example to construct our own DSL and has question for this line (samples-java/DynamicDslWorkflow.java at main · temporalio/samples-java · GitHub)

activities.executeAsync(
                    action.getFunctionRef().getRefName(),
                    ActResult.class,
                    workflowData.getCustomer())

And the activities is constructed in this untyped way

activities = Workflow.newUntypedActivityStub(activityOptions);

Have 2 questions:
(1) how activity can be executed by its type name? Is “type name” referring to activity name such as rejectApplication here?
(2) if one workflow is registered with multiple activities and
i. activities may have same name, will this still work?
ii. activities will have unique name, will this still work?

Thanks!

  1. Yes, activity type is by default the name of your activity method with first letter upper-cased, in that case “RejectApplication”
    activities.executeAsync also takes in a resultClass type which has to match the return type.
    You can change the default type name via annotation in your activity interface, for example:
@ActivityMethod(name = "abc")
String executeActivity();

In the DSL sample, the activity type comes from the json/yaml definitions, and it’s assumed that all activities take in a Customer type as input for sake of demo.

  1. For this sample it’s assumed that all activities user can specify in the DSL are already pre-registered in DslActivitiesImpl. So if in the sample you define a function ref in json/yaml for an activity which does not exist, it would not be found. Alternatively you could use DynamicActivity interface to register a single activity impl with the worker that can handle any activity type. See for example here.

i. if you mean by invoking the same activity type multiple times during workflow execution, yes
ii. i don’t fully understand this question, sorry could you provide example?

Thanks for the quick reply. The example could be:

TemporalWorkerClientContextWithImpl<?> workerClientContext = new TemporalWorkerClientContextWithImpl<>(
                Constants.TASK_QUEUE,
                Collections.singletonList(new activity1Impl(), new activity2Impl()),
                Workflow.class,
                WorkflowImpl.class)

and uses this context to setup worker and factory.

So, this workflow has 2 registered activities.
for sub-question1, activity1 has a method called test, will activity.execute("Test", any(), any()) still work?
for sub-question2, if activity1 has a method called test and activity2 also has a method called test, will activity.execute("Test", any(), any()) still work?

Hope this makes sense.

Thanks for the clarification.

When you register multiple activity implementations you do have to think about the activity methods (types) that you are registering. If activity impl A and B for example both have activity methods “doSomething” (“DoSomething” activity type). You will get error when you try to register A and B with same worker:

"DoSomething" activity type is already registered with the worker

One way to get around that is to use activity name prefix that is a parameter on your ActivityInterface annotations, for example:

@ActivityInterface(namePrefix = "A_")
public interface Activity1Interface { 
  String doSomething(String input);
}

@ActivityInterface(namePrefix = "B_")
public interface Activity2Interface { 
   String doSomething(String input);
}

namePrefix is prepended to the activity method name to generate the activity type so they are unique in this case.

So for sub-questions 1 and 2, if “Test” is not a uniquely registered activity type, I think your code will fail on activity registration with the worker, so you would not be able to get to the activity.execute("Test", any(), any()) part.
Hope this answers these questions, let me know if it does not :slight_smile:

Yes, this is helpful and answers my question. Just want to double check with you,

(1) For

@ActivityInterface(namePrefix = "A_")
public interface Activity1Interface { 
  String doSomething(String input);
}

It gets to

activity.execute("A_doSomething", any(), any())

(2) For

@ActivityMethod(name = "abc")
String executeActivity();

It gets to

activity.execute("Abc", any(), any())

Is that correct?

  1. The type would be “A_DoSomething” (first letter of activity method is going to be capitalized)

  2. In that case since you explicitly set the name it would be “abc”

There is another case where you could have like:

@ActivityInterface(namePrefix = "A_")
public interface Activity1Interface { 
  String doSomething(String input);
 
  @ActivityMethod(name = "abc")
  String doSomethingElse();
}

here the two activity types would be “A_DoSomething” and “abc”. So when you explicitly define the type via @ActivityMethod(name = "...") it will be uses as defined regardless of prefix or the method name.

1 Like

Gotcha. Thanks for the clarification! This is super clear now.

1 Like

Hi @tihomir,

Sorry got a follow up question. Noticed you mentioned in a slack thread that we can also use dynamic activity to invoke the user-defined class that maps to the activity type (slack). I’d like to learn more what could be the trade-offs between these 2 options: the first one is using the normal workflow activity such as what your DSL example code used; the second is using dynamic activity as you mentioned in that slack channel. What could be the recommended approach? Thanks!

I think it depends on how you need to map the serverless workflow dsl function definitions to your activities.

In SW workflow states you don’t define your function instructions, but reference them by name, for example:

"states":[
  {
     "name":"Greet",
     "type":"operation",
     "actions":[
        {
           "functionRef": "My Greeting Function"
        }
     ],
     "transition": "Next Workflow State..."
  }
]

functionRef is a reference to one of the functions you define in your functions definition, for example:

"functions": [
     {
        "name": "My Greeting Function",
        "operation": "http://myapis.org/applicationapi.json#greet",
        "type": "rest"
     }
   ]

note that function definitions can be completely externalized from the actual dsl workflow definition, so instead of defining all your functions inline, you can set them up in a separate json/yaml file and then in your workflow definition reference that file, for example:

"functions": "myfunctiondefinitions.json"

This makes your pre-defined function definitions reusable across multiple dsl workflow definitions.

Idea behind this was that if you have end users define your DSL workflows, you can provide them with pre-defined functions they can reference in their control flow logic (workflow states). Same is really for event definitions, retry definitions, etc.

If you allow your end users to define their own function definitions, meaning you do not know up-front how to map each of their defined function defs, then imo using DynamicActivity comes in really handy as it will make sure that you will map every single one of user-defined functions to activities, and then can use for example the operation and type params to determine what code needs to be called for that activity.

If you provide the functions to your end users, and force them to use these pre-defined function definitions, then you can set up all your activity implementations up-front and will know that you will have a mapping for every single one that user defines in their workflow states.

Being able to pre-define your function defs that your users are allowed to use has the advantage that you can have different configurations per activity, for example you can rate-limit them differently if needed, can provide separate retry policies etc. In case of using DynamicActivity you only have a single activity impl that you have to configure as a single one.

Hope this helps.

1 Like

You can also use a combination of “real” activity impls and DynamicActivity.
You can set up a number of pre-defined activities that users can reference and map to, but then also set up DynamicActivity as a “fallback” type of activity impl that will be called for all activities that you don’t have concrete impls for. Note tho that you can only register a single impl of DynamicActivity per worker.

1 Like