dapr / java-sdk

Dapr SDK for Java
Apache License 2.0
262 stars 207 forks source link

Spring Boot, Workflows and dependency injection #1147

Open jakesmolka opened 1 month ago

jakesmolka commented 1 month ago

Ask your question here

Without further knowledge about the Dapr Java SDK, is dependency injection for workflow activities (and probably workflows itself) not supported at the moment?

Check this example here: https://github.com/jakesmolka/dapr-workflow/blob/6629ad6935165a49147754187fc7b3ba4d073817/workflow-lib/src/main/java/org/example/flows/CreateResourceActivity.java

I get the following error, when running the workflow worker:

== APP == org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'worker' defined in URL [jar:nested:/Users/jake.smolka/Documents/code/temp/dapr-workflow/workflow-worker/target/workflow-worker-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/org/example/workflowworker/Worker.class]: Failed to instantiate [org.example.workflowworker.Worker]: Constructor threw exception
== APP ==   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1337) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971) ~[spring-context-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[spring-context-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.2.jar!/:3.3.2]
== APP ==   at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.2.jar!/:3.3.2]
== APP ==   at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.2.jar!/:3.3.2]
== APP ==   at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.2.jar!/:3.3.2]
== APP ==   at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.2.jar!/:3.3.2]
== APP ==   at org.example.workflowworker.WorkflowWorkerApplication.main(WorkflowWorkerApplication.java:10) ~[!/:0.0.1-SNAPSHOT]
== APP ==   at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
== APP ==   at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
== APP ==   at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
== APP ==   at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
== APP ==   at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:91) ~[workflow-worker-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
== APP ==   at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:53) ~[workflow-worker-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
== APP ==   at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:58) ~[workflow-worker-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
== APP == Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.example.workflowworker.Worker]: Constructor threw exception
== APP ==   at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:221) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:94) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1331) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   ... 23 common frames omitted
== APP == Caused by: java.lang.RuntimeException: No constructor found for activity class 'org.example.flows.CreateResourceActivity'.
== APP ==   at io.dapr.workflows.runtime.ActivityWrapper.<init>(ActivityWrapper.java:40) ~[dapr-sdk-workflows-0.12.0.jar!/:na]
== APP ==   at io.dapr.workflows.runtime.WorkflowRuntimeBuilder.registerActivity(WorkflowRuntimeBuilder.java:93) ~[dapr-sdk-workflows-0.12.0.jar!/:na]
== APP ==   at org.example.workflowworker.Worker.<init>(Worker.java:22) ~[!/:0.0.1-SNAPSHOT]
== APP ==   at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
== APP ==   at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
== APP ==   at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
== APP ==   at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[na:na]
== APP ==   at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[na:na]
== APP ==   at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:195) ~[spring-beans-6.1.11.jar!/:6.1.11]
== APP ==   ... 25 common frames omitted
== APP == Caused by: java.lang.NoSuchMethodException: org.example.flows.CreateResourceActivity.<init>()
== APP ==   at java.base/java.lang.Class.getConstructor0(Class.java:3585) ~[na:na]
== APP ==   at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2754) ~[na:na]
== APP ==   at io.dapr.workflows.runtime.ActivityWrapper.<init>(ActivityWrapper.java:37) ~[dapr-sdk-workflows-0.12.0.jar!/:na]
== APP ==   ... 33 common frames omitted

When removing the entire dependency injection it works (compiles, run, behaves as expected).

When using deprecated @Autowired field injection the worker compiles and runs but just gets a null object.

jakesmolka commented 1 month ago

cc @salaboy, if you don't mind, since we talked about this

salaboy commented 1 month ago

Let's work into supporting this with the new starters.. there is no code in the SDK to support that injection yet.

olitomlinson commented 4 weeks ago

@salaboy just to be clear are you saying that both Workflows and Activities should support dependency injection in the Java SDK?

As it stands, the dotnet experience for Workflows does not allow dependency injection for Workflows, but it does for Activities. This is due to the desire to keep non-deterministic behaviours out of the workflow code, as injected dependencies are considered a risk for containing non-deterministic behaviour.

I know that the language SDKs have to follow the idioms of their languages, and rightly so, but this feels like something we should strive for parity on across all the SDKs. Thoughts?

cc @WhitWaldo

salaboy commented 3 weeks ago

@olitomlinson yeah.. in my opinion, that is what a Spring Boot user would expect. Without that the experience is very clunky.

WhitWaldo commented 3 weeks ago

I agree that the SDKs should absolutely take a uniform approach. As @olitomlinson said, right now, .NET allows injection into activities, but not workflows because the former are intended to be non-deterministic, but the latter strictly must be.

This came up in Discord about whether workflows should allow injection for logging purposes and while I can see the case for it being made available on the workflow context itself (which is resolved from DI), I still don't think workflows themselves should allow injection.

salaboy commented 3 weeks ago

Good point.. I will look into that.

On Tue, Oct 29, 2024 at 10:19 AM Whit Waldo @.***> wrote:

I agree that the SDKs should absolutely take a uniform approach. As @olitomlinson https://github.com/olitomlinson said, right now, .NET allows injection into activities, but not workflows because the former are intended to be non-deterministic, but the latter strictly must be.

This came up in Discord about whether workflows should allow injection for logging purposes and while I can see the case for it being made available on the workflow context itself (which is resolved from DI), I still don't think workflows themselves should allow injection.

— Reply to this email directly, view it on GitHub https://github.com/dapr/java-sdk/issues/1147#issuecomment-2443808662, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACCMXVD4E6GBJNRA3MKFZTZ55OJHAVCNFSM6AAAAABQDE6PR2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDINBTHAYDQNRWGI . You are receiving this because you were mentioned.Message ID: @.***>

--