Building Distributed and Event-driven Applications in Java or Scala With Akka on OpenShift - Archived

Akka is a framework that allows developers to build event-driven and distributed applications on top of the JVM. Akka has native support for two different JVM languages – Java and Scala. You can choose one of them or mix-and-match them based on the task and then deploy the resulting application using Akka.

At Red Hat we work on a similar project called Vert.x. Vert.x is “Node.js done right” (quote by me) and provides a stable event-driven framework for JVM. Akka is more high-level (but also low-level in other cases) than Vert.x. Akka is an actor system on top of JVM (Scala & Java) compared to Vert.x being a general framework for building applications. Akka could be built on top of Vert.x.

Akka has several interesting features and properties, but before moving forward, let’s take a look at Akka’s description:

Build powerful concurrent & distributed applications more easily.
 
Akka is a toolkit and runtime for building highly concurrent, distributed, and fault tolerant event-driven applications on the JVM.

Some may say it’s just a lot of buzzwords, but there is more to it than that. Let’s take a look at some of the great stuff Akka enables.

Event driven

Event-driven has become highly popularized by the Node.js framework, because on JavaScript, it’s common for code to be driven by events. However, it’s not a feature just of Node.js, any framework can be event driven.

Akka, however, builds on top of a bit different model than Node.js. Akka uses Actors to abstract pieces of work and messages to trigger events on Actors. Every actor get’s a message box that receives messages from other actors. Actors consume messages and process them. The result is then sent to other actors.

This whole concept encourages a functional style of thinking. It creates applictions built with no functional side-effects and no global state. Actors should be standalone entities that can do their work without touching shared data preventing the triggering of side-effects and no use of global state. The Actor model is an old concept and is also implemented for many different languages but Akka brings it to the front.

Distributed

While the Akka framework can be run as a single node, where the Actors live in the same JVM. Such an application would have limitations in performance, availability, etc. To get over such a limitation, Akka can be deployed in a distributed manner. You deploy the Akka framework on many different machines and the nodes then communicate with each other.

To get a degree of high-availability, Akka provides self-healing functionalities. In case some node in the cluster goes missing, Akka will try to redeploy and heal the cluster to continue the work.

Highly concurrent

Because the Actors are independent and communicate over messages, Akka makes parallelizing the computation very easy. There are several ways to do this, but the general idea is that with no global state and no side-effects by the actors, the actors may be run in parallel without issues.

Actors are actually very small units and per the Akka website, it should be possible to deploy more than 2.5 million Actors per 1GB of heap. From this we can read, that an Actor is not a single JVM, nor a single thread. Actor is very lightweight and is internally mapped to some thread pool that executes the computation of actors.

Actors

Let’s take a look at what an Akka actor looks like in Scala

case class Greeting(who: String)
 
class GreetingActor extends Actor with ActorLogging {
  def receive = {
    case Greeting(who) ? log.info("Hello " + who)
  }
}

and in Java

public class Greeting {
 
  private String greeting;
 
  public String getGreeting() {
    return this.greeting;
  }  
 
  public void setGreeting(String greeting) {
    this.greeting = greeting;
  }  
 
}
 
public class GreetingActor extends UntypedActor {
 
  LoggingAdapter log = Logging.getLogger(getContext().system(), this);
 
  public void onReceive(Object message) throws Exception {
    if (message instanceof Greeting) {
      log.info("Hello {}", message.getGreeting());
    } else
      unhandled(message);
  }
 
}

What happens here is, we first create a class that will represent a greeting with the receiver information. Second, we create an Actor and define its the receive method. The receive method is triggered for every method that arrives into the Actor’s mailbox. In the method, we check if the message is a Greeting we defined before, and in the case where it is we log ‘Hello ‘ with the extracted name of the receiver of the greeting.

That’s it!

Deploying Akka on OpenShift

To deploy Akka on OpenShift we will not use any special community cartridge, just a simple DIY cartridge that is provided natively by OpenShift. To handle the application life-cycle, Maven is going to be used. Also today we will not get into clustering Akka, but instead we will just start the web server. This first exercise is just to get Akka on OpenShift.

Creating the OpenShift application

First, let’s start by creating an OpenShift environment where we will host the application. You must have OpenShift client tools setup already. You will also need Git installed.

rhc app create akka diy
cd akka

Now we have a basic application. Into the repository we will create our application and then once we push it to OpenShift, the code will be built and the application started.

Mavenizing

Maven is de-facto standard for dependency and life-cycle management in Java. Scala seems to prefere Sbt as a tool, though I prefer Maven to Sbt. It would also be possible to use Gradle, but that would require a more complex set up on OpenShift. Since Maven is already pre-installed, it makes the most sense to use it today.

In the root directory of your repository, create a new file pom.xml with content

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>eu.mjelen.akka</groupId>
    <artifactId>akka</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <dependencies>
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_2.10</artifactId>
            <version>2.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.mashupbots.socko</groupId>
            <artifactId>socko-webserver_2.10</artifactId>
            <version>0.3.1</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>2.10.2</scalaVersion>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>eu.mjelen.akka.Main</mainClass>
                </configuration>
            </plugin>
 
        </plugins>
    </build>
 
</project>

It’s a long text because of the verbosity of XML, but let’s take a look at the file step by step.

First we define our project. Every project needs GroupId and ArtifactId … for those I use “eu.mjelen.akka” and “akka” respectively.

The next step is to define dependencies. We have 2 dependencies – Akka and Socko. Socko is a web server implemented using the awesome Netty library that nicely integrates into Akka and provides a clear interface to expose Actors as web requests.

Then we define three plugins. The first one configures the compiler plugin to work with Java 7. The second plugin integrates Scala compiler into the lifecycle. With the Scala plugin, when we run “maven compile,” both the existing Java and Scala source will be compiled. The last plugin allows us to execute Java code from Maven. We specify the mainClass with the name of a class that will serve as the entry point to our application.

One more file is needed for Maven. Create new file maven.xml with content

<settings>
  <localRepository>${OPENSHIFT_DATA_DIR}maven</localRepository>
</settings>

This is needed to configure Maven to behave correctly in the secured environment of OpenShift.

And that’s it.

Setting up the directory structure

Our directory structure will be very simple and on a Unix system could be created with single command

mkdir -p src/main/scala/eu/mjelen/akka

Also we can clean up the repository from the example Ruby application:

rm -rf README.md diy misc

Background on Scala syntax

I use Scala and not everyone may not be as familiar with it, so I wanted to provide some background.

First is the type specification. Scala can infer most types, so usually you don’t need to write types of variables, however when you do need, it’s done like this.

  val system:ActorSystem = null

is an equivalent to Java’s

  public ActorSystem system = null;

Next is the => constructor. The constructor separates the predicate from the body that is executed in case it’s matched.

  case event: HttpRequestEvent => {}

In case an event is of type HttpRequestEvent … execute the code after the =>.

To learn more, check the Scala documentation and especially the cheat-sheet.

Application entry point

The entry point will be Main class and will be used to start the application. Create a new file src/main/scala/eu/mjelen/akka/Main.scala

package eu.mjelen.akka
 
import akka.actor.{Props, ActorSystem}
import org.mashupbots.socko.routes.{Routes, GET}
import org.mashupbots.socko.webserver.{WebServer, WebServerConfig}
 
object Main {
 
  val system:ActorSystem = ActorSystem("OpenShift")
 
  val routes = Routes({
    case GET(request) => {
      system.actorOf(Props[Core]) ! request
    }
  })
 
  def main(args:Array[String]) {
    val webServer = new WebServer(WebServerConfig("OpenShift", sys.env("OPENSHIFT_DIY_IP"), sys.env("OPENSHIFT_DIY_PORT").toInt), routes, system)
    webServer.start()
  }
 
}

When the application starts it will call the main method on the class named eu.mjelen.akka.Main. This is the source of the class. First we import required classes from other libraries. After that we define new object … object is Scala’s equivalent to Java’s static members on a class.

The code in the body of the class is equivalent to Java’s constructor. In the constructor we create new ActorSystem – group of actors – named OpenShift.

Second we define routes using simple DSL. In our case we react to all GET requests. When the GET request comes, the block of the code after the arrow is executed.

system.actorOf(Props[Core]) ! request

system references the ActorSystem, thus a context in which Actors live. From the context we try to get an actor that is implemented using a class Core (definition will come later). Props is Akka’s abstraction for Actor configuration. For every request it will create new Actor instance. ! is a method on the Actor. ! method is used to send a message to an Actor. In our case we send the Actor the request.

In the main method we create new webserver with configuration, predefined routes and our Actor system. Lastly we start the server.

Actor request handler

The code first. The Core class will be stored in a file src/main/scala/eu/mjelen/akka/Core.scala

package eu.mjelen.akka
 
import akka.actor.Actor
import java.util.Date
import org.mashupbots.socko.events.HttpRequestEvent
 
class Core extends Actor {
 
  def receive = {
 
    case event: HttpRequestEvent =>
      event.response.write("Hello from Socko (" + new Date().toString + ")")
      context.stop(self)
  }
 
}

Again, first we import the dependencies. Then we define new class that inherits from Actor. An instance of this class will be created for every request. The method receive is called for every message sent to the actor. In our case for every request. We test the message if it’s an HTTP request. In case it is, we write a response. Finally we discard the current instance of the Actor.

Commit the application

Now we have the application in place, let’s commit and try to push to OpenShift

git add -A .
git commit -m "My application"
git push origin master

You should see output similar to this

Counting objects: 12, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (11/11), 1.68 KiB | 0 bytes/s, done.
Total 11 (delta 0), reused 0 (delta 0)
remote: Stopping DIY cartridge
remote: Building git ref 'master', commit 607bd12
remote: Preparing build for deployment
remote: Deployment id is af2ac5b9
remote: Activating deployment
remote: Starting DIY cartridge
remote: -------------------------
remote: Git Post-Receive Result: success
remote: Activation status: success
remote: Deployment completed with status: success
To ssh://53034ba95973ca6bea0000d7@akka-mjelen.rhcloud.com/~/git/akka.git/
   9ae5a26..607bd12  master -> master

However if you try to access the page, in my case using the URL

http://akka-mjelen.rhcloud.com/

You should get 503 error page. That’s because we did not tell OpenShift how to start the application. Let’s move to that.

Build the application

We have configured maven to handle the builds and dependencies for us. Now we need to execute it on OpenShift.

With standard cartridges, OpenShift knows how to start and stop applications – this is true for al the cartridges except one – DIY, which is what we used. In this case we need to provide two executables that will be used by OpenShift to start and stop the application. The files are already there in our repository, we just need to change them.

Starting the application

To start the application let’s edit the .openshift/action_hooks/start script. Replace the content with

#!/bin/bash
 
cd ${OPENSHIFT_REPO_DIR}
 
export M2_HOME=/etc/alternatives/maven-3.0
export PATH=$JAVA_HOME/bin:$M2_HOME/bin:$PATH
 
mvn --global-settings ${OPENSHIFT_REPO_DIR}maven.xml compile -Popenshift -DskipTests
 
nohup mvn --global-settings ${OPENSHIFT_REPO_DIR}maven.xml exec:java -Popenshift -DskipTests > $OPENSHIFT_DIY_DIR/logs/server.log 2>&1 &

What does it do? First we enter the root directory of our application. Then we need to configure some basic for Maven. Then we run maven to compile the application. For this line, Maven is run without redirecting the output and thus the output will be visible as part of the git push.

Lastly we start maven to execute our application and this time we redirect the output to log file and also disconnect standard output and error from Git so git can safely disconnect and keep the application running in the backgorund.

Stopping the application

The last step is to let OpenShift know how to stop the application. This time edit a file .openshift/action_hooks/stop and put this content there:

#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH
 
if [ -z "$(ps -ef | grep maven | grep -v grep)" ]
then
    client_result "Application is already stopped"
else
    kill `ps -ef | grep maven | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
fi

This is just a variation in the original DIY script. First we check if the same Maven process is running, in case there is, we send the kill signal to it. If it’s not, we print a message.

Deploying

Now, just use git to push the code and see what happens.

git add .
git commit -m "Application management logic"
git push origin master

Once the process finishes, there should be an application accessible over HTTP.

Conclusion

Today we deployed Akka on OpenShift in a very non-automated way.

OpenShift provides a way to automate these steps by using a cartridge, however, I wanted first show you that it’s reasonably simple to add support for not-officially-supported frameworks to OpenShift. In a following blog post we will explore the distributed capabilities of Akka and will probably dig into building an Akka cartridge.

Next Steps

Categories
Java, OpenShift Container Platform, OpenShift Online, OpenShift Origin
Tags
, , , ,
Comments are closed.