Skip to content

Commit

Permalink
Distributed application with Akka Remoting
Browse files Browse the repository at this point in the history
  • Loading branch information
paoloambrosio committed Nov 26, 2016
1 parent ee30902 commit 5735a10
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 21 deletions.
11 changes: 8 additions & 3 deletions README.md
Expand Up @@ -6,16 +6,21 @@ write good Scala or Akka code.


It has no tests, but on the other side there isn't much code either! It has no tests, but on the other side there isn't much code either!


Run the application locally: Run the application locally (each command on different terminal):
```sh ```sh
sbt frontend/run REMOTE_PORT=2551 sbt backend/run
REMOTE_PORT=2552 sbt backend/run
REMOTE_PORT=2553 BACKEND_NODES=127.0.0.1:2551,127.0.0.1:2552 sbt frontend/run
curl http://localhost:9000/greet/World curl http://localhost:9000/greet/World
``` ```


Run it on Kubernetes (Minikube): Run it on Kubernetes (Minikube):
```sh ```sh
eval $(minikube docker-env) eval $(minikube docker-env)
sbt docker:publishLocal sbt docker:publishLocal
kubectl create -f kubernetes/ kubectl create -f kubernetes/backend-service.yaml
kubectl create -f kubernetes/backend-deployment.yaml
kubectl create -f kubernetes/frontend-service.yaml
kubectl create -f kubernetes/frontend-deployment.yaml
curl $(minikube service frontend-service --url)/greet/World curl $(minikube service frontend-service --url)/greet/World
``` ```
10 changes: 10 additions & 0 deletions backend/src/main/scala/com/example/backend/BackendApp.scala
@@ -0,0 +1,10 @@
package com.example.backend

import com.example.ExampleApp

object BackendApp extends App with ExampleApp {

system.actorOf(Backend.props, "backend")

log.info("Backend Service started")
}
42 changes: 41 additions & 1 deletion build.sbt
@@ -1,17 +1,57 @@
import BuildSettings._ import BuildSettings._
import Dependencies._ import Dependencies._
import Resolvers._ import Resolvers._
import com.typesafe.sbt.packager.archetypes.JavaAppPackaging.autoImport.bashScriptExtraDefines
import com.typesafe.sbt.packager.docker._


lazy val commonSettings = buildSettings ++ Seq( lazy val commonSettings = buildSettings ++ Seq(
resolvers += akkaHttpJsonResolvers resolvers += akkaHttpJsonResolvers
) )


lazy val dockerSettings = Seq(
dockerExposedPorts := Seq(Docker.akkaTcpPort),
bashScriptExtraDefines := Seq(Docker.bashExports)
)

lazy val frontendDockerSettings = dockerSettings ++ Seq(
dockerCommands := Seq(
Cmd("FROM", "java:latest"),
Cmd("USER", "root"),
ExecCmd("RUN", "apt-get", "-qq", "update"),
ExecCmd("RUN", "apt-get", "-yq", "install", "dnsutils"),
ExecCmd("RUN", "apt-get", "clean"),
ExecCmd("RUN", "rm", "-rf", "/var/lib/apt/lists/*")
) ++ dockerCommands.value.filterNot {
case Cmd("FROM", _) => true
case _ => false
},
bashScriptExtraDefines := Seq(Docker.frontendBashExports)
)

lazy val root = (project in file(".")) lazy val root = (project in file("."))
.settings(buildSettings: _*) .settings(buildSettings: _*)
.aggregate(frontend) .aggregate(frontend, backend)

lazy val common = project
.settings(commonSettings: _*)
.settings(libraryDependencies ++= akka)


lazy val frontend = project lazy val frontend = project
.settings(commonSettings: _*) .settings(commonSettings: _*)
.settings(libraryDependencies ++= akkaHttp) .settings(libraryDependencies ++= akkaHttp)
.enablePlugins(JavaAppPackaging) .enablePlugins(JavaAppPackaging)
.enablePlugins(DockerPlugin) .enablePlugins(DockerPlugin)
.settings(frontendDockerSettings)
.dependsOn(`backend-api`, common)

lazy val backend = project
.settings(commonSettings: _*)
.settings(libraryDependencies ++= akka)
.enablePlugins(JavaAppPackaging)
.enablePlugins(DockerPlugin)
.settings(dockerSettings)
.dependsOn(`backend-api`, common)

lazy val `backend-api` = project
.settings(commonSettings: _*)
.settings(libraryDependencies ++= akka)
23 changes: 23 additions & 0 deletions common/src/main/resources/reference.conf
@@ -0,0 +1,23 @@
akka {

actor {
provider = remote
}

remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = ${app.remote.interface}
port = ${app.remote.port}
}
}
}

app {
remote {
interface = "127.0.0.1"
interface = ${?REMOTE_INTERFACE}
port = 0
port = ${?REMOTE_PORT}
}
}
13 changes: 13 additions & 0 deletions common/src/main/scala/com/example/ExampleApp.scala
@@ -0,0 +1,13 @@
package com.example

import akka.actor.ActorSystem
import akka.event.LoggingAdapter
import com.typesafe.config.ConfigFactory

trait ExampleApp {

val actorSystemName = "example"
val config = ConfigFactory.load()
implicit val system = ActorSystem(actorSystemName, config)
val log: LoggingAdapter = system.log
}
8 changes: 3 additions & 5 deletions frontend/src/main/resources/application.conf
@@ -1,12 +1,10 @@
akka {
actor {
}
}

app { app {
http { http {
interface = "0.0.0.0" interface = "0.0.0.0"
port = 9000 port = 9000
port = ${?HTTP_PORT} port = ${?HTTP_PORT}
} }
backend {
nodes = ${?BACKEND_NODES}
}
} }
41 changes: 41 additions & 0 deletions frontend/src/main/scala/com/example/BackendPathsConfig.scala
@@ -0,0 +1,41 @@
package com.example

import com.typesafe.config.Config

trait BackendPathsConfig {

def actorSystemName: String
def config: Config

def backendPaths() = remoteNodesAddress.map(node => s"$node/user/backend")

private def remoteNodesAddress: List[String] = {
val remoteInterface = config.getString("app.remote.interface")
val remotePort = config.getInt("app.remote.port")
if (config.hasPath("app.backend.nodes")) {
config.getString("app.backend.nodes")
.split(',').toList
.map(toAkkaNodeRef(remotePort))
.flatten
} else if (remotePort > 0) {
List(toAkkaNodeRef(remoteInterface, remotePort))
} else {
List.empty
}
}

private def toAkkaNodeRef(defaultPort: Int)(peer: String): Option[String] = {
peer.trim.split(':') match {
case Array(host, port) =>
Some(toAkkaNodeRef(host, port.toInt))
case Array(host) =>
if (defaultPort == 0) None
else Some(toAkkaNodeRef(host, defaultPort))
}
}

private def toAkkaNodeRef(host: String, port: Int) = {
s"akka.tcp://$actorSystemName@$host:$port"
}

}
16 changes: 5 additions & 11 deletions frontend/src/main/scala/com/example/frontend/FrontendApp.scala
@@ -1,25 +1,19 @@
package com.example.frontend package com.example.frontend


import akka.actor.{ActorRef, ActorSystem} import akka.actor.ActorRef
import akka.event.LoggingAdapter import akka.routing.RoundRobinGroup
import akka.util.Timeout import akka.util.Timeout
import com.example.backend.Backend import com.example.{BackendPathsConfig, ExampleApp}
import com.typesafe.config.ConfigFactory


import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContext
import scala.concurrent.duration._ import scala.concurrent.duration._


object FrontendApp extends App object FrontendApp extends App with ExampleApp with BackendPathsConfig
with HttpServerStartup with FrontendRestApi { with HttpServerStartup with FrontendRestApi {


val actorSystemName = "example"
val config = ConfigFactory.load()

implicit val system = ActorSystem(actorSystemName, config)
override implicit val executionContext: ExecutionContext = system.dispatcher override implicit val executionContext: ExecutionContext = system.dispatcher
val log: LoggingAdapter = system.log


override val backend: ActorRef = system.actorOf(Backend.props, "backend") override val backend: ActorRef = system.actorOf(RoundRobinGroup(backendPaths()).props(), "backend")
override implicit val backendTimeout: Timeout = 5 seconds override implicit val backendTimeout: Timeout = 5 seconds


val interface = config.getString("app.http.interface") val interface = config.getString("app.http.interface")
Expand Down
15 changes: 15 additions & 0 deletions kubernetes/backend-deployment.yaml
@@ -0,0 +1,15 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: backend-deployment
spec:
replicas: 1
template:
metadata:
labels:
app: backend
cluster: example
spec:
containers:
- name: backend
image: backend:0.1
11 changes: 11 additions & 0 deletions kubernetes/backend-service.yaml
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP
clusterIP: None
selector:
app: backend
ports:
- port: 2551
3 changes: 3 additions & 0 deletions kubernetes/frontend-deployment.yaml
Expand Up @@ -14,3 +14,6 @@ spec:
image: frontend:0.1 image: frontend:0.1
ports: ports:
- containerPort: 9000 - containerPort: 9000
env:
- name: BACKEND_DISCOVERY_SERVICE
value: backend-service
3 changes: 2 additions & 1 deletion project/Build.scala
Expand Up @@ -15,7 +15,8 @@ object Dependencies {
private val akkaHttpV = "2.4.11" private val akkaHttpV = "2.4.11"


val akka = Seq( val akka = Seq(
"com.typesafe.akka" %% "akka-actor" % akkaV "com.typesafe.akka" %% "akka-actor" % akkaV,
"com.typesafe.akka" %% "akka-remote" % akkaV
) )
val akkaHttp = Seq( val akkaHttp = Seq(
"com.typesafe.akka" %% "akka-http-experimental" % akkaHttpV, "com.typesafe.akka" %% "akka-http-experimental" % akkaHttpV,
Expand Down
32 changes: 32 additions & 0 deletions project/Docker.scala
@@ -0,0 +1,32 @@
object Docker {

val akkaTcpPort = 2551

val bashExports = """
export REMOTE_INTERFACE=$(hostname --ip-address)
# Configure the default remote port unless passed
if [ -z "$REMOTE_PORT" ]; then
export REMOTE_PORT=""" + akkaTcpPort + """
fi
"""

val frontendBashExports = """
# Configure backend nodes from service if passed
# otherwise keep them as they are
if [ ! -z "$BACKEND_DISCOVERY_SERVICE" ]; then
BACKEND_NODES=$(
host -t A $BACKEND_DISCOVERY_SERVICE | \
grep 'has address' |
cut -d ' ' -f 4 |
xargs |
sed -e 's/ /,/g'
)
if [ ! -z "$BACKEND_NODES" ]; then
export BACKEND_NODES
fi
fi
""" + bashExports

}

0 comments on commit 5735a10

Please sign in to comment.