Complex workflow dependencies

On our current platform, our workflow uses a Checkpointer class to record specific events to our DB. We’d like to move to Temporal, but want to keep the checkpointer class for recording a semantic history of the workflow in the same format as done currently.

The checkpointer wraps the DB service, which is a clear candidate for an activity, but the checkpointer itself uses generics (the output of each checkpoint is generic), lambdas (allowing the client to inject custom logic per checkpoint), and types (to deal with Java type erasure…) in its method signatures, all of which aren’t standard POJO elements, suggesting that the checkpointer interface can’t be an activity interface.

My intuition would be to inject an activity stub into the checkpointer and inject the checkpointer into the workflow, but the workflow must have a parameterless constructor.

Do I need to take on a static dependency (call new Checkpointer(acitivityStub, other, deps, ...)) in the workflow code, or is there another way I should be approaching this problem?

After more investigating, I see that Worker has an addWorkflowImplementationFactory method, and it appears to allow me to inject dependencies to the workflow instance.

I assume this is the answer, but any additional comments or insight on the proper usage are appreciated.

1 Like

There is a reason that injecting dependencies into workflow is not directly supported. Direct calls to external systems like DB are not deterministic as they can fail intermittently.

You still can use that class from the workflow code. But make sure to throw java.lang.Error on any intermittent failures. It would fail a workflow task instead of corrupting workflow execution.

But I think the best way would be refactoring your checkpointer to two components. The first would reside inside a local activity and another inside a workflow. And all communication between them would be through serializable value objects. The workflow component would not need to be injected as it will not contain any direct DB dependencies.

1 Like

Also any code inside the workflow is reexecuted on recovery. You can reduce produced side effects using Workflow.isReplaying flag. But this assumes that workflow state is not affected by the code protected by this flag in any way.

lambdas (allowing the client to inject custom logic per checkpoint)

worries me, as it sounds like the lambdas might be used to affect workflow state

I appreciate the need to ensure that a workflow is deterministic. Our current infrastructure also uses replays to support fault-tolerance (although our framework compares to Temporal like notepad compares to IntelliJ…)

My use-case could be better described thus: we have several activities that are used repeatedly in the same way within and across workflows; we want to encapsulate this repeated behavior into a shareable, stateless unit; what would be the best way to inject such a multi-activity component into workflows?

If the interface to the multi-activity component was comprised of only POJOs, then I would probably just make the component itself an activity or a child-workflow.

In this particular case, I’ll try the implementation factory to inject the component with pre-configured activity stubs.

Why do you need to inject it at all? Could you just construct it inside the workflow code directly? Something like:

MyStatelessUnit unit = new MyStatelessUnit();
unit.whatever();

Easy of composition and unit testing.

For example, this particular unit may have two or three dependencies (activities). Rather than require that each workflow take on those dependencies, just to be able to instantiate their own instance of the component, I can consolidate the management of those dependencies into one place.

For unit testing of the workflow, it is nice to mock out the component, rather than needing to mock out the individual activities and their interactions.

But yes, there is no technical reason why I couldn’t take a static dependency on the component code, via new or other static method. It’s primarily a matter of convenience and maintainability.

Workflow can use OO techniques. So you can make all your code injectable. And then treat @WorkflowMethod code as main which configures the components. And during unit tests you can create workflows that inject different component implementations.