26 oct 2020

¿Y el servidor en Scala? Akka está.

 Continuando con esta seguidilla de posts sobre Scala y retomando desde el post anterior Ahora vamos a a hacer un experimento un poquito más avanzado: vamos a intentar crear un servidor web con Scala.

Akka es aparentemente el servidor http mas famoso del ecosistema de Scala. Según lo que se puede leer en el sitio de Akka.io, “Akka es un conjunto de herramientas para crear aplicaciones basadas en mensajes altamente concurrentes, distribuidas y resilientes para Java y Scala.” Este incluye tanto un servidor http que implementa el patrón Actor Model como un cliente para hacer llamadas http.

En este post mi plan es tratar de hacer un pequeño proyecto en Scala utilizando Visual Studio Code, que implemente un servidor que pueda responder a un request GET, con un “Hi, Akka server is running” o algo por el estilo.

Primero voy a abrir VS Code en una carpeta vacía y crearé un archivo build.sbt con las siguientes entradas:

lazy val akkaHttpVersion = "10.2.9"
lazy val akkaVersion    = "2.6.18"
lazy val root = (project in file(".")).
  settings(
    inThisBuild(List(
      organization    := "com.foy",
      scalaVersion    := "2.13.8"
    )),
    name := "AkkaTest",
     libraryDependencies ++= Seq(
    "com.typesafe.akka" %% "akka-http"                % akkaHttpVersion,     
    "com.typesafe.akka" %% "akka-actor-typed"         % akkaVersion,
    "com.typesafe.akka" %% "akka-stream"              % akkaVersion,   
    "ch.qos.logback"    % "logback-classic"           % "1.2.3",
    "org.scalatest"     %% "scalatest"                % "3.1.4"         % Test
  )
)

Según entiendo esta es la mínima configuración y dependencias necesarias para crear el más básico de los ejemplos de un servidor Akka

Ahora voy a crear la ruta normal de los archivos de Scala: src/main/scala y dentro de esta voy a crear un archivo main.Scala.


Dentro de este archivo voy a escribir el código necesario para crear una ruta llamada “Hi” que representa la llamada get y cuando esta llamada se complete, vamos a devolver el texto plano “Hi, Foy Akka server es running!”:
package com.foy.scala.AkkaTest

import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route

object SimpleRouter {
  val route: Route = path("Hi") {
    get {
      complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Hi, Foy Akka server is running!"))
    }
  }
}
Ahora añadiré el objeto principal que llamaré App que contendrá el método main que es el responsable de levantar el servidor. El código completo de archivo main.Scala quedaría algo así:
package com.foy.scala.AkkaTest

import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpRequest

import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContextExecutor}

import scala.util.Failure
import scala.util.Success

object SimpleRouter {
  val route: Route = path("Hi") {
    get {
      complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Hi, Foy Akka server is running!"))
    }
  }
}

object App {

  //Function to start the httpServer
  private def startHttpServer(routes: Route)(implicit system: ActorSystem[_]): Unit = {
    import system.executionContext
    val serverBinding = Http().newServerAt("localhost", 8080).bind(routes)
    serverBinding.onComplete {
      case Success(binding) =>
        val address = binding.localAddress
        system.log.info("Foy Akka Server online at http://{}:{}/", address.getHostString, address.getPort)
      case Failure(ex) =>
        system.log.error("Foy Akka Server Failed to bind HTTP endpoint, terminating system", ex)
        system.terminate()
    }
  }

  def main(args: Array[String]): Unit = {

    val rootBehavior = Behaviors.setup[Nothing] { context =>    
      startHttpServer(SimpleRouter.route)(context.system)
      Behaviors.empty
    }

    val system = ActorSystem[Nothing](rootBehavior, "akkaServerTest")
  }
}
Abrimos una terminal y arrancamos el servidor sbt

Podemos compilar nuestro código y  asegurarnos que todo esté bien ejecutamos el comando “compile” (dentro del server no hace escribir sbt antes). Y finalmente usamos el comando “run” para iniciar nuestro servidor Akka:


Como podemos observar, debido al log que pusimos en nuestro código, nos indica que nuestro servidor está corriendo, además de en cual ip y en cual puerto.  Ahora probemos nuestro endpoint “Hi”, como es un endpoint get podemos usar un Browser para acceder a la ruta http://localhost:8080/Hi la cual nos muestra el mensaje que especificamos en la resolución de la ruta:
O lo podemos hacer con cliente REST cualquiera

!Y ya tenemos un servidor corriendo! 

Tratemos de entender un poco el código: El objeto simpleRouter establece una variable inmutable de tipo Route en la cual establecemos el path “Hi”, definimos que este cuando se invoque con el verbo get y que cuando se complete devolveremos un objeto de tipo httpEntity (el response según yo entiendo) que simplemente contendrá el texto que queremos. En el objeto App tenemos la función startHttpServer que inicia propiamente el objeto Http que representa nuestro servidor, el cual al iniciarlo le pasamos la ruta (o ip) y el puerto, por el cual responderá y además le pasamos, por medio del método bind, el ruteo que manejará. 

Hasta aquí está todo relativamente normal. Pero entonces ¿Qué esto esto de Actors y Behaviors? En nuestro ejemplo actual si lo notan no estamos usando nada de eso, cuando nos piden el parámetro ActorSystem le estamos pasando Nothing y como behavior estamos usando Empty. Sin embargo, los Actores, como se señaló al inicio del post, son el corazón de Akka y el siguiente post voy a tratar de explicarme de que va este asunto.

Antes de cerrar el post voy a refactorizar un poco, voy crear un archivo que se llame SimpleRouter.Scala para poner ahí el objeto que devuelve la ruta. Queda más o menos así:









Roy {aka. Foy}

Autor & Editor

Desarrallador y líder técnico, con experiencia en tecnologías Microsoft desde los tiempos del VB6 y el asp clásico hasta el .Net Core, pasando por COM+, javascript, angularjs, Ionic, xaml, cordova, MVC, Web Api, Sql Server, Oracle... . Ávido lector, apasionado programador.