From 71f377e9a31de4b2a60ab9048ef0bb6896ed8888 Mon Sep 17 00:00:00 2001 From: Adriano Santos Date: Wed, 11 Oct 2023 11:31:31 -0300 Subject: [PATCH] Feat. Added simple dependency injector auxiliary class --- README.md | 107 +++++++++++++++++- pom.xml | 2 +- .../java/io/eigr/spawn/api/ActorIdentity.java | 5 +- src/main/java/io/eigr/spawn/api/ActorRef.java | 3 + .../eigr/spawn/api/actors/ActorContext.java | 5 +- .../api/extensions/DependencyInjector.java | 8 ++ .../extensions/SimpleDependencyInjector.java | 35 ++++++ .../transport/server/ActorServiceHandler.java | 1 + src/test/java/io/eigr/spawn/SpawnTest.java | 7 +- .../test/actors/ActorWithConstructor.java | 6 +- .../java/io/eigr/spawn/test/extensions/A.java | 5 + .../test/extensions/AImplementation.java | 8 ++ .../SimpleDependencyInjectorTest.java | 27 +++++ 13 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 src/main/java/io/eigr/spawn/api/extensions/DependencyInjector.java create mode 100644 src/main/java/io/eigr/spawn/api/extensions/SimpleDependencyInjector.java create mode 100644 src/test/java/io/eigr/spawn/test/extensions/A.java create mode 100644 src/test/java/io/eigr/spawn/test/extensions/AImplementation.java create mode 100644 src/test/java/io/eigr/spawn/test/extensions/SimpleDependencyInjectorTest.java 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); + } +}