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
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.
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:
And see that everything works.
If you have found this post useful, please leave a comment. Thanks!
$ 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!
No comments:
Post a Comment