Wednesday, February 19, 2020

Akka and Spring Boot



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:

  1. The Spring boot as a parent
  2. Dependency for spring starter web to enable usage of Spring REST controllers
  3. 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!

No comments:

Post a Comment