Thread safety and reusability of Stubs and Implementations

From the documentation, it’s not immediately clear what can be a DI Scope of Activity/Workflow Implementation/Stub. What can be a singleton and what should be created for each invocation of the workflow?

Activity Implementation
Activity implementations are Singletons, stateless, and reused across workflows and worker threads.

Activity Stub
Should each instance of a workflow implementation create or use its own set of activity stubs?
Is it a correct implementation to create an activity stub separately as a singleton and inject it into each instance of a workflow implementation?
Or should one activity stub be used only in one workflow implementation instance?

Workflow Implementation
Can we have only one instance of Workflow implementation and return it every time inside the factory passed to addWorkflowImplementationFactory? If this Workflow implementation is stateless (doesn’t store anything in the instance fields) and thread-safe.

Workflow Stub
Can we have only one instance of Workflow stub if we don’t need to pass different options and use it for triggering the workflow from parallel executors? I think that probably no and one workflow stub - one workflow id (because we can pass workflowId as a parameter when we create a stub), but let’s make this question complete.

These questions probably boil down to questions if Activity/Workflow stub/implementation has a state and if it’s thread-safe. But it also could be influenced by the semantic of our abstractions.
This question rises when we use dependency injection to initialize activities and workflows.

3 Likes

Activity Implementation
Correct, a single activity instance is registered with a worker and servers all the activity invocation requests.

Activity Stub
Yes, an activity stub is a per workflow object instance. They cannot be shared across workflow instances. In general, any call to Workflow. is expected to be per workflow instance.

Workflow Implementation
While reusing it might work for some cases I would advise against returning the same Workflow implementation object from the factory. So always create a new workflow instance per factory invocation. While dependency injection to workflow objects is possible it would require a custom workflow instance-specific scope. Until such scope is supported don’t use dependency injection of workflows. Instead inject dependencies into activity objects and return them as activity results to the workflow.

Workflow Stub
No, workflow stub is per child workflow instance. The reason is that it is stateful and can be used to send signals to the same child after it was started.

Do activity stubs need to be created IN the workflow implementation, or can they be created outside of it?

For example:

public void start() {
    Worker workflowWorker = factory.newWorker(WORKFLOW_QUEUE);
    workflowWorker.addWorkflowImplementationFactory(HelloWorld.class, () -> new HelloWorldImpl(Workflow.newActivityStub(HelloActivity.class)));

    Worker activityWorker = factory.newWorker(ACTIVITY_QUEUE);
    activityWorker.registerActivitiesImplementations(new HelloActivityImpl("Hello"));

    factory.start();
}

as opposed to

public class HelloWorld {
    private final HelloActivity helloActivity = Workflow.newActivityStub(HelloActivity.class);
    // etc.
}

They should be created in a context of a workflow. A workflow object instantiation is done in the context of a workflow, so having them as a field (non static) is fine.

I believe most of the samples use this approach. For example:

public class AccountTransferWorkflowImpl implements AccountTransferWorkflow {

  private final ActivityOptions options =
      ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(5)).build();
  private final Account account = Workflow.newActivityStub(Account.class, options);

  @Override
  public void transfer(
      String fromAccountId, String toAccountId, String referenceId, int amountCents) {
    account.withdraw(fromAccountId, referenceId, amountCents);
    account.deposit(toAccountId, referenceId, amountCents);
  }
}

What is the consequence of NOT initializing them in the workflow context, as long as they are unique instances per workflow?

It is not possible. Any attempt to use Workflow… methods outside of the workflow context result in an exception.