Skip to content

Commit

Permalink
Feat. Added simple dependency injector auxiliary class
Browse files Browse the repository at this point in the history
  • Loading branch information
sleipnir committed Oct 11, 2023
1 parent fb0dc80 commit 71f377e
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 12 deletions.
107 changes: 102 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)


Expand Down Expand Up @@ -93,7 +94,7 @@ The second thing we have to do is add the spawn dependency to the project.
<dependency>
<groupId>com.github.eigr</groupId>
<artifactId>spawn-java-std-sdk</artifactId>
<version>v0.9.1</version>
<version>v1.0.0</version>
</dependency>
```
We're also going to configure a few things for our application build to work, including compiling the protobuf files.
Expand Down Expand Up @@ -127,7 +128,7 @@ See below a full example of the pom.xml file:
<dependency>
<groupId>com.github.eigr</groupId>
<artifactId>spawn-java-std-sdk</artifactId>
<version>v0.9.1</version>
<version>v1.0.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
Expand Down Expand Up @@ -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<Actor.State> 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:
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>io.eigr.spawn</groupId>
<artifactId>spawn-java-std-sdk</artifactId>
<packaging>jar</packaging>
<version>0.9.0</version>
<version>1.0.0</version>
<name>spawn-java-std-sdk</name>
<url>http://maven.apache.org</url>

Expand Down
5 changes: 3 additions & 2 deletions src/main/java/io/eigr/spawn/api/ActorIdentity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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) {
Expand Down Expand Up @@ -58,6 +58,7 @@ public Optional<String> getMaybeParent() {
}

public boolean isParent() {
System.out.println(String.format("Actor %s is parent? = %s", this.name, this.maybeParent.isPresent()));
return this.maybeParent.isPresent();
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/io/eigr/spawn/api/ActorRef.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ protected static ActorRef of(SpawnClient client, Cache<ActorOuterClass.ActorId,
ActorOuterClass.ActorId actorId;

if (identity.isParent()) {
System.out.println("Passou como parent");
actorId = buildActorId(identity.getSystem(), identity.getName(), identity.getParent());

spawnActor(actorId, client);
} else {
System.out.println("Passou como normal");
actorId = buildActorId(identity.getSystem(), identity.getName());
}

Expand All @@ -56,6 +58,7 @@ protected static ActorRef of(SpawnClient client, Cache<ActorOuterClass.ActorId,
}

if (identity.hasLookup()) {
System.out.println("Faca lookup");
spawnActor(actorId, client);
}

Expand Down
5 changes: 4 additions & 1 deletion src/main/java/io/eigr/spawn/api/actors/ActorContext.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package io.eigr.spawn.api.actors;

import io.eigr.spawn.api.Spawn;
import io.eigr.spawn.api.extensions.DependencyInjector;

import java.util.Optional;
import java.util.StringJoiner;

public final class ActorContext<S extends Object> {
private Spawn spawn;

private Optional<S> state;

private DependencyInjector injector;

public ActorContext(Spawn spawn){
this.spawn = spawn;
this.state = Optional.empty();
Expand All @@ -28,6 +30,7 @@ public Optional<S> getState() {
return state;
}


@Override
public String toString() {
return new StringJoiner(", ", ActorContext.class.getSimpleName() + "[", "]")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.eigr.spawn.api.extensions;

public interface DependencyInjector {

<T extends Object> void bind(Class<T> type, Object instance);

<T extends Object> T getInstance(Class<T> type);
}
Original file line number Diff line number Diff line change
@@ -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<Class<?>, 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 <T> void bind(Class<T> 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> T getInstance(Class<T> type) {
return type.cast(this.bucket.get(type));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/test/java/io/eigr/spawn/SpawnTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions src/test/java/io/eigr/spawn/test/extensions/A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.eigr.spawn.test.extensions;

public interface A {
String say(String name);
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 71f377e

Please sign in to comment.