diff --git a/README.md b/README.md
index 4f797bb..b9e77d6 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ JVM User Language Support for [Spawn](https://github.com/eigr/spawn).
1. [Overview](#overview)
2. [Getting Started](#getting-started)
3. [Advanced Use Cases](#advanced-use-cases)
+ - [Dependency Injection](#dependency-injection)
- [Types of Actors](#types-of-actors)
- [Stateless Actors](#stateless-actors)
- [Considerations about Spawn actors](#considerations-about-spawn-actors)
@@ -14,16 +15,16 @@ JVM User Language Support for [Spawn](https://github.com/eigr/spawn).
- [Forward](#forward)
- [Pipe](#pipe)
- [State Management](#state-management)
-4. [Using Actors](#using-actors)
+5. [Using Actors](#using-actors)
- [Call Named Actors](#call-named-actors)
- [Call Unnamed Actors](#call-unnamed-actors)
- [Async](#async)
- [Timeouts](#timeouts)
-5. [Deploy](#deploy)
+6. [Deploy](#deploy)
- [Defining an ActorSystem](#defining-an-actorsystem)
- [Defining an ActorHost](#defining-an-actorhost)
- [Activators](#activators)
-6. [Actor Model](#actor-model)
+7. [Actor Model](#actor-model)
- [Virtual Actors](#virtual-actors)
@@ -93,7 +94,7 @@ The second thing we have to do is add the spawn dependency to the project.
com.github.eigr
spawn-java-std-sdk
- v0.9.1
+ v1.0.0
```
We're also going to configure a few things for our application build to work, including compiling the protobuf files.
@@ -127,7 +128,7 @@ See below a full example of the pom.xml file:
com.github.eigr
spawn-java-std-sdk
- v0.9.1
+ v1.0.0
ch.qos.logback
@@ -518,6 +519,102 @@ Spawn Actors abstract a huge amount of developer infrastructure and can be used
In the sections below we will demonstrate some features available in Spawn that contribute to the development of
complex applications in a simplified way.
+### Dependency Injection
+
+Sometimes we need to pass many arguments as dependencies to the Actor class.
+In this case, it is more convenient to use your own dependency injection mechanism.
+However, the Spawn SDK already comes with an auxiliary class to make this easier for the developer.
+Let's look at an example:
+
+1. First let's take a look at some example dependency classes:
+
+```java
+// We will have an interface that represents any type of service.
+public interface MessageService {
+ String getDefaultMessage();
+}
+
+// and concrete implementation here
+public class MessageServiceImpl implements MessageService {
+ @Override
+ public String getDefaultMessage() {
+ return "Hello Spawn in English";
+ }
+}
+```
+
+2. Second, let's define an actor so that it receives an instance of the DependencyInjector class through the class constructor:
+
+```java
+package io.eigr.spawn.test.actors;
+
+import io.eigr.spawn.api.actors.ActorContext;
+import io.eigr.spawn.api.actors.Value;
+import io.eigr.spawn.api.actors.annotations.Action;
+import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor;
+import io.eigr.spawn.api.extensions.DependencyInjector;
+import io.eigr.spawn.java.test.domain.Actor;
+
+@StatefulNamedActor(name = "test_actor_constructor", stateType = Actor.State.class)
+public final class ActorWithDependencies {
+
+ private final MessageService messageService;
+
+ public ActorWithConstructor(DependencyInjector injector) {
+ // Note how to use dependency injection here to get a concrete class of MessageService.
+ this.defaultMessage = injector.getInstance(MessageService.class);
+ }
+
+ @Action(inputType = Actor.Request.class)
+ public Value setLanguage(Actor.Request msg, ActorContext context) {
+ if (context.getState().isPresent()) {
+ }
+
+ return Value.at()
+ .response(Actor.Reply.newBuilder()
+ .setResponse(messageService.getDefaultMessage())
+ .build())
+ .state(updateState("java"))
+ .reply();
+ }
+
+ private Actor.State updateState(String language) {
+ return Actor.State.newBuilder()
+ .addLanguages(language)
+ .build();
+ }
+}
+
+```
+
+3. Then you can pass your dependent classes this way to your Actor:
+```java
+package io.eigr.spawn.java.demo;
+
+import io.eigr.spawn.api.Spawn;
+import io.eigr.spawn.api.extensions.DependencyInjector;
+import io.eigr.spawn.api.extensions.SimpleDependencyInjector;
+
+public class App {
+ public static void main(String[] args) {
+ DependencyInjector injector = SimpleDependencyInjector.createInjector();
+ injector.bind(MessageService.class, new MessageServiceImpl());
+
+ Spawn spawnSystem = new Spawn.SpawnSystem()
+ .create("spawn-system")
+ .withActor(Joe.class, actorConstructorArgs, injector -> new Joe((DependencyInjector) injector))
+ .build();
+
+ spawnSystem.start();
+ }
+}
+```
+
+It is important to note that this helper mechanism does not currently implement any type of complex dependency graph.
+Therefore, it will not build objects based on complex dependencies nor take care of the object lifecycle for you.
+In other words, all instances added through the bind method of the SimpleDependencyInjector class will be singletons.
+This mechanism works much more like a bucket of objects that will be forwarded via your actor's constructor.
+
### Types of Actors
First we need to understand how the various types of actors available in Spawn behave. Spawn defines the following types of Actors:
diff --git a/pom.xml b/pom.xml
index 70f4328..04e8480 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
io.eigr.spawn
spawn-java-std-sdk
jar
- 0.9.0
+ 1.0.0
spawn-java-std-sdk
http://maven.apache.org
diff --git a/src/main/java/io/eigr/spawn/api/ActorIdentity.java b/src/main/java/io/eigr/spawn/api/ActorIdentity.java
index f92b2cd..9cee5d6 100644
--- a/src/main/java/io/eigr/spawn/api/ActorIdentity.java
+++ b/src/main/java/io/eigr/spawn/api/ActorIdentity.java
@@ -14,7 +14,7 @@ public final class ActorIdentity {
private ActorIdentity(String system, String name, String parent, boolean lookup){
this.system = system;
this.name = name;
- this.maybeParent = Optional.ofNullable(parent);
+ this.maybeParent = Optional.of(parent);
this.lookup = lookup;
}
@@ -26,7 +26,7 @@ private ActorIdentity(String system, String name, boolean lookup){
}
public static ActorIdentity of(String system, String name) {
- return new ActorIdentity(system, name, true);
+ return new ActorIdentity(system, name, false);
}
public static ActorIdentity of(String system, String name, String parent) {
@@ -58,6 +58,7 @@ public Optional getMaybeParent() {
}
public boolean isParent() {
+ System.out.println(String.format("Actor %s is parent? = %s", this.name, this.maybeParent.isPresent()));
return this.maybeParent.isPresent();
}
diff --git a/src/main/java/io/eigr/spawn/api/ActorRef.java b/src/main/java/io/eigr/spawn/api/ActorRef.java
index ab24b5a..f12652e 100644
--- a/src/main/java/io/eigr/spawn/api/ActorRef.java
+++ b/src/main/java/io/eigr/spawn/api/ActorRef.java
@@ -43,10 +43,12 @@ protected static ActorRef of(SpawnClient client, Cache {
private Spawn spawn;
-
private Optional state;
+ private DependencyInjector injector;
+
public ActorContext(Spawn spawn){
this.spawn = spawn;
this.state = Optional.empty();
@@ -28,6 +30,7 @@ public Optional getState() {
return state;
}
+
@Override
public String toString() {
return new StringJoiner(", ", ActorContext.class.getSimpleName() + "[", "]")
diff --git a/src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java b/src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java
new file mode 100644
index 0000000..667fcf1
--- /dev/null
+++ b/src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java
@@ -0,0 +1,8 @@
+package io.eigr.spawn.api.extensions;
+
+public interface DependencyInjector {
+
+ void bind(Class type, Object instance);
+
+ T getInstance(Class type);
+}
diff --git a/src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java b/src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java
new file mode 100644
index 0000000..4b08754
--- /dev/null
+++ b/src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java
@@ -0,0 +1,35 @@
+package io.eigr.spawn.api.extensions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class SimpleDependencyInjector implements DependencyInjector {
+
+ private final Map, Object> bucket;
+
+ private SimpleDependencyInjector() {
+ this.bucket = new HashMap<>();
+ }
+
+ private static class SimpleDependencyInjectorHelper{
+ private static final SimpleDependencyInjector INSTANCE = new SimpleDependencyInjector();
+ }
+
+ public static DependencyInjector createInjector(){
+ return SimpleDependencyInjectorHelper.INSTANCE;
+ }
+
+ @Override
+ public void bind(Class type, Object instance) {
+ if (this.bucket.containsKey(type)) {
+ throw new IllegalArgumentException("There is already an instance associated with this type in the bucket");
+ }
+
+ this.bucket.put(type, instance);
+ }
+
+ @Override
+ public T getInstance(Class type) {
+ return type.cast(this.bucket.get(type));
+ }
+}
diff --git a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java
index 977e811..913f3a4 100644
--- a/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java
+++ b/src/main/java/io/eigr/spawn/internal/transport/server/ActorServiceHandler.java
@@ -14,6 +14,7 @@
import io.eigr.spawn.api.actors.ActorFactory;
import io.eigr.spawn.api.actors.workflows.SideEffect;
import io.eigr.spawn.api.exceptions.ActorInvocationException;
+import io.eigr.spawn.api.extensions.SimpleDependencyInjector;
import io.eigr.spawn.internal.Entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/test/java/io/eigr/spawn/SpawnTest.java b/src/test/java/io/eigr/spawn/SpawnTest.java
index a4aa3a5..9b8805b 100644
--- a/src/test/java/io/eigr/spawn/SpawnTest.java
+++ b/src/test/java/io/eigr/spawn/SpawnTest.java
@@ -6,6 +6,8 @@
import io.eigr.spawn.api.TransportOpts;
import io.eigr.spawn.api.exceptions.ActorCreationException;
import io.eigr.spawn.api.exceptions.ActorInvocationException;
+import io.eigr.spawn.api.extensions.DependencyInjector;
+import io.eigr.spawn.api.extensions.SimpleDependencyInjector;
import io.eigr.spawn.java.test.domain.Actor;
import io.eigr.spawn.test.actors.ActorWithConstructor;
import io.eigr.spawn.test.actors.JoeActor;
@@ -23,10 +25,13 @@ public class SpawnTest {
@Before
public void before() throws Exception {
+ DependencyInjector injector = SimpleDependencyInjector.createInjector();
+ injector.bind(String.class, "Hello with Constructor");
+
spawnSystem = new Spawn.SpawnSystem()
.create("spawn-system")
.withActor(JoeActor.class)
- .withActor(ActorWithConstructor.class, "Hello with Constructor", arg -> new ActorWithConstructor((String) arg))
+ .withActor(ActorWithConstructor.class, injector, arg -> new ActorWithConstructor((DependencyInjector) arg))
.withTransportOptions(
TransportOpts.builder()
.port(8091)
diff --git a/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java b/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java
index b16e487..0cbdea8 100644
--- a/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java
+++ b/src/test/java/io/eigr/spawn/test/actors/ActorWithConstructor.java
@@ -4,14 +4,16 @@
import io.eigr.spawn.api.actors.Value;
import io.eigr.spawn.api.actors.annotations.Action;
import io.eigr.spawn.api.actors.annotations.stateful.StatefulNamedActor;
+import io.eigr.spawn.api.extensions.DependencyInjector;
import io.eigr.spawn.java.test.domain.Actor;
@StatefulNamedActor(name = "test_actor_constructor", stateType = Actor.State.class)
public final class ActorWithConstructor {
+
private final String defaultMessage;
- public ActorWithConstructor(String defaultMessage) {
- this.defaultMessage = defaultMessage;
+ public ActorWithConstructor(DependencyInjector injector) {
+ this.defaultMessage = injector.getInstance(String.class);
}
@Action(inputType = Actor.Request.class)
diff --git a/src/test/java/io/eigr/spawn/test/extensions/A.java b/src/test/java/io/eigr/spawn/test/extensions/A.java
new file mode 100644
index 0000000..1210321
--- /dev/null
+++ b/src/test/java/io/eigr/spawn/test/extensions/A.java
@@ -0,0 +1,5 @@
+package io.eigr.spawn.test.extensions;
+
+public interface A {
+ String say(String name);
+}
diff --git a/src/test/java/io/eigr/spawn/test/extensions/AImplementation.java b/src/test/java/io/eigr/spawn/test/extensions/AImplementation.java
new file mode 100644
index 0000000..e0391f7
--- /dev/null
+++ b/src/test/java/io/eigr/spawn/test/extensions/AImplementation.java
@@ -0,0 +1,8 @@
+package io.eigr.spawn.test.extensions;
+
+public class AImplementation implements A {
+ @Override
+ public String say(String name) {
+ return String.format("Hello %s", name);
+ }
+}
diff --git a/src/test/java/io/eigr/spawn/test/extensions/SimpleDependencyInjectorTest.java b/src/test/java/io/eigr/spawn/test/extensions/SimpleDependencyInjectorTest.java
new file mode 100644
index 0000000..fbb8466
--- /dev/null
+++ b/src/test/java/io/eigr/spawn/test/extensions/SimpleDependencyInjectorTest.java
@@ -0,0 +1,27 @@
+package io.eigr.spawn.test.extensions;
+
+import io.eigr.spawn.api.extensions.DependencyInjector;
+import io.eigr.spawn.api.extensions.SimpleDependencyInjector;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class SimpleDependencyInjectorTest {
+
+ private DependencyInjector injector;
+
+ @Before
+ public void before() {
+ injector = SimpleDependencyInjector.createInjector();
+ }
+
+ @Test
+ public void testInjection() {
+ injector.bind(A.class, new AImplementation());
+
+ A implementation = injector.getInstance(A.class);
+ String actual = implementation.say("Spawn");
+ assertEquals("Hello Spawn", actual);
+ }
+}