Skip to content

Spring proxies break @Workflow/@Step annotation discovery #298

@LuaKT

Description

@LuaKT

Hi,
I'm hitting an issue with trying to use this when workflow impl classes are Spring proxied due to @Transactional on the methods in the class.

For example, a setup like this fails:

@Service
public class AccountSyncWorkflowImpl implements AccountSyncWorkflow {

    private AccountSyncWorkflow proxy;

    public void setProxy(AccountSyncWorkflow proxy) {
        this.proxy = proxy;
    }

    @Override
    @Workflow(name = "syncAccount")
    public void syncAccount(UUID accountId) {
        // workflow body...
    }

    @Transactional(readOnly = true)
    public void loadAccount(UUID accountId) {
        // db read
    }
}

@Configuration
public class AccountSyncWorkflowConfig {

    private final AccountSyncWorkflowImpl workflowImpl;

    public AccountSyncWorkflowConfig(AccountSyncWorkflowImpl workflowImpl) {
        this.workflowImpl = workflowImpl; // Spring-managed bean, proxied due to @Transactional
    }

    @Bean
    @Primary
    public AccountSyncWorkflow accountSyncWorkflow() {
        var proxy = DBOS.registerWorkflows(AccountSyncWorkflow.class, workflowImpl);
        workflowImpl.setProxy(proxy);
        return proxy;
    }
}

DBOS.startWorkflow(() -> accountSyncWorkflow.syncAccount(UUID.randomUUID()));

Will error with:

java.lang.RuntimeException: Only @Workflow functions may be called from the startWorkflow lambda
        at dev.dbos.transact.internal.DBOSInvocationHandler.invoke(DBOSInvocationHandler.java:60)
        at jdk.proxy3/jdk.proxy3.$Proxy116.syncAccount(Unknown Source)
...

The issue is that in DBOSInvocationHandler.invoke, getAnnotation is called on the proxy which doesn't have the annotation.

public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
var implMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
implMethod.setAccessible(true);
var hook = hookHolder.get();
var wfTag = implMethod.getAnnotation(Workflow.class);
if (wfTag != null) {
return handleWorkflow(implMethod, args, wfTag, hook);
}

I think instead the real class should be resolved and the annotation info cached when the workflow is registered, then in invoke it uses this cache instead of doing reflection on every invocation.

Thank you

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions