Both
Akka and
Spring, and more specifically
Spring Boot are common frameworks in the Java domain. However the integration of Akka and Spring is not strait forward. There are other guides about this integration, for example:
baeldung's post, but I felt a need to simplify the process.
In this post we will create a Spring Boot application that includes Spring integration with Akka.
The application includes a REST controller that upon activation, sends a message to an Akka actor.
The Akka actor includes Spring injection enabling it to use a Spring service.
The pom.xml file
To create the application we will start with the pom.xml file.
The pom.xml file includes:
- The Spring boot as a parent
- Dependency for spring starter web to enable usage of Spring REST controllers
- Dependency for Akka
<?xml version="1.0" encoding="UTF-8"?><project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
</parent>
<groupId>org.demo</groupId>
<artifactId>akka</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.13</artifactId>
<version>2.6.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The Application
The next step is to create the Spring entry point, which is the Spring boot application.
Nothing special here, just invoking the spring boot application.
package org.demo.akka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
The Actors System Producer
The actors system producer provides an ActorSystem bean.
This means that we can later auto wire the actors system.
package org.demo.akka;
import akka.actor.ActorSystem;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class MyActorSystemProducer {
@Bean
public ActorSystem actorSystem() {
return ActorSystem.create("demo-akka");
}
}
The Actors Producer
The actors producer is a service that enables us to create new actors bean that we need.
It uses an auto wired ActorsSystem (and we can auto wire thanks to the actors system producer).
package org.demo.akka;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@Service
public class MyActorsProducer {
@Autowired
private volatile ApplicationContext applicationContext;
@Autowired
private ActorSystem actorSystem;
public ActorRef createActor(String actorBeanName, String actorName) {
Props props = Props.create(MySpringActorProducer.class, applicationContext, actorBeanName);
return actorSystem.actorOf(props, actorName);
}
}
Notice that it uses a MySpringActorProducer class.
This class is an Akka "indirect actor producer, which implement the actual actor instance creation using Spring beans.
package org.demo.akka;
import akka.actor.Actor;
import akka.actor.IndirectActorProducer;
import org.springframework.context.ApplicationContext;
public class MySpringActorProducer implements IndirectActorProducer {
private ApplicationContext applicationContext;
private String beanActorName;
public MySpringActorProducer(ApplicationContext applicationContext, String beanActorName) {
this.applicationContext = applicationContext;
this.beanActorName = beanActorName;
}
@Override
public Actor produce() {
return (Actor) applicationContext.getBean(beanActorName);
}
@Override
public Class<? extends Actor> actorClass() {
return (Class<? extends Actor>) applicationContext.getType(beanActorName);
}
}
The Actor and the Service
Let's create an actor that uses a service. Notice that the actor uses auto wire to find the service. This is possible due to the actors producer that we have previously implemented. The actor class should be implemented using a prototype scope, which means that we create a new instance upon each bean request.
The (simple) service is:
package org.demo.akka;
import org.springframework.stereotype.Service;
@Service
public class MyService {
public String greet(String name) {
return "Hello " + name;
}
}
And the Actor is:
package org.demo.akka;
import akka.actor.UntypedAbstractActor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyActor extends UntypedAbstractActor {
@Autowired
private MyService myService;
@Override
public void onReceive(Object message) {
String name = (String) message;
getSender().tell(myService.greet(name), getSelf());
}
}
The REST Controller
To complete the implementation, let's create a REST controller that creates an actor.
package org.demo.akka;
import akka.actor.ActorRef;
import akka.pattern.Patterns;
import akka.util.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;
import java.util.concurrent.TimeUnit;
@RestController
public class MyRestController {
@Autowired
private MyActorsProducer actorsProducer;
@RequestMapping("/actor")
public Object sendToActor(@RequestBody String name) throws Exception {
ActorRef greeter = actorsProducer.createActor("myActor", "myActorName");
FiniteDuration duration = FiniteDuration.create(1, TimeUnit.SECONDS);
Timeout timeout = Timeout.durationToTimeout(duration);
Future<Object> future = Patterns.ask(greeter, name, timeout);
return Await.result(future, duration);
}
}
Final Notes
We can test the application using curl:
$ curl localhost:8080/actor --data-binary "John"
Hello John
And see that everything works.
If you have found this post useful, please leave a comment. Thanks!