The Spring Framework and especially Spring Boot, are very popular and widely used for programming applications in Java. For Scala, there are other popular frameworks, particularly for programming web servers, such as the Play Framework.

In this small series of articles, we want to explore whether and to what extent we can program functionally in Scala while still using Spring Boot.

The Spring Framework consists of a whole range of elements, some of which form a kind of central core while others, building on the core, have more of a library character.

Dependency Injection

Among the central elements is a mechanism for dependency injection, also called auto-wiring. In various highly configurable ways, this finds classes that appear as arguments or members in other classes or methods and instantiates them automatically at runtime.

Annotations

Spring and Spring Boot also rely heavily on annotations to mark classes and methods for various purposes and to add code to them. Alternatively, this can at least partially be done via a configuration file (XML or YAML), but that isn‘t very popular since these files can quickly become very large.

Spring Boot

Spring Boot is a collection of libraries designed to make handling and getting started with the Spring Framework easier. This happens primarily through so-called starters, which bring many defaults with them and are intended to make it easier to implement typical use cases.

A list of starters can be found here.

The focus is primarily on web servers and database connections.

Example Application

In a small example, we‘ll now explore what this looks like in practice with Scala and as much functional programming as possible.

We‘ll implement a very simple (and morbid) game in which armadillos venture onto a highway and may get run over. Based on a functional core, we‘ll use Spring Boot to implement a REST API for the game.

Let‘s start with the functional core. A Dillo (short for Armadillo) has a name and a health level. When it gets run over, the health level decreases depending on the speed. Additionally, it can say hello as long as it‘s still alive:

case class Dillo(name: String, health: Int)

object Dillo {
  def runOver(d: Dillo, speed: Int): Dillo = {
    d.copy(health = Math.max(0, d.health - speed))
  }

  def speak(d: Dillo): String = {
    if (d.health > 0)
      s"${d.name} says Hi!"
    else
      s"${d.name} is dead"
  }
}

Then we define a highway with armadillos that are on it, and a method that lets the Dillos say something:

case class Highway(dillos: Map[UUID, Dillo]) {
  def report(): Iterable[String] = {
    for (_id, d) <- dillos
      yield Dillo.speak(d)
  }
}

We abstract functions that do something with the highway as Highway.Op - a so-called arrow. One of them is driveAlong, which runs over all armadillos once. A second one adds a new armadillo to the highway:

object Highway {
  type Op = Highway => Highway

  val empty: Highway = Highway(Map.empty)

  def driveAlong(speed: Int): Op = { highway =>
    highway.copy(dillos = highway.dillos.view.mapValues(Dillo.runOver(_, speed)).toMap)
  }

  def spawn(id: UUID, dillo: Dillo): Op = { highway =>
    highway.copy(dillos = highway.dillos + (id -> dillo))
  }
}

So much for the functional core of our application. Now let‘s move on to the REST API.

For our example, it‘s sufficient to include the Spring Boot starter for web applications: spring-boot-starter-web. In SBT we can do this as follows:

libraryDependencies += "org.springframework.boot" % "spring-boot-starter-web" % "3.4.2"

Then we first define a class that represents our application:

import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
class DemoApp {}

The SpringBootApplication annotation implicitly brings a whole series of additional annotations with it. In particular, the so-called ComponentScan, which causes the Spring Framework to find classes in the application‘s classpath for dependency injection (so-called components). Also the EnableAutoConfiguration annotation from Spring Boot, which sets certain defaults depending on the libraries in the classpath and the presence (or absence!) of certain classes in the namespace of the application class. Therefore, just by adding a class or annotation, or by adding a library, the behavior of the application can change.

As the entry and starting point of our application, we now define a main method for the companion object of DemoApp:

object DemoApp {
  def main(args: Array[String]): Unit = {
    import org.springframework.boot.SpringApplication

    SpringApplication.run(classOf[DemoApp], args: _*)
  }
}

This is enough to have an executable application that first outputs a wonderful Spring logo:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.2)

followed by some log messages showing that Spring Boot by default starts an embedded Apache Tomcat web server on port 8080 and registers our application in it as a servlet.

However, this is initially still empty. So let‘s define a so-called Controller to process HTTP requests. The easiest way to do this is with a class using the RestController annotation:

import org.springframework.web.bind.annotation.{GetMapping, RestController}

@RestController
class DemoController {
  @GetMapping() def index: String = "Hello world"
}

Spring Boot finds this class and binds it accordingly into the servlet. If we now start the application and send a request to "/", we get a corresponding response:

$ curl http://localhost:8080/
Hello World

However, to make our game „playable“ via such a RestController, we need to figure out how to manage the state of the game (or of the highway) over time. Since Spring Boot unfortunately doesn‘t offer any functional interfaces for this, we have to work imperatively. What we can do, however, to delay this decision as long as possible, is define a trait:

trait IAppState {
  def get: Highway
  def update(op: Highway.Op): Unit
}

We can then use this in our controller to return the highway report as a response to "/":

@RestController
class DemoController(state: IAppState) {
  @GetMapping(value = Array("/")) def index: String =
    state.get.report().mkString("\n")
}

If we now start our application, Spring complains that it can‘t find an implementation of IAppState. So let‘s define an implementation that stores the state in an AtomicReference:

import org.springframework.stereotype.Component

@Component
class MemAppState extends IAppState {
  import java.util.concurrent.atomic.AtomicReference
  
  private val highway: AtomicReference[Highway] = AtomicReference[Highway](Highway.empty)

  def get: Highway = highway.get()

  def update(op: Highway.Op): Unit = {
    highway.updateAndGet({ highway => op(highway) })
  }
}

Through the Component annotation, we make the class discoverable for Spring. The framework creates an instance of our MemAppState class when the application starts in order to create the controller with it. If multiple classes exist that implement IAppState, Spring aborts the application with a corresponding error, since it can‘t decide which one to use.

So that something can actually be changed on the highway via the HTTP interface, we now define a request to add an armadillo and one to drive along the highway:

@RestController
class DemoController(state: IAppState) {
  @GetMapping() def index: String =
    state.get.report().mkString("\n")

  @PostMapping(Array("/dillo")) def dillo(name: String): UUID = {
    val id = UUID.randomUUID()
    state.update(Highway.spawn(id, Dillo(name, health = 100)))
    id
  }

  @PostMapping(Array("/drive")) def drive(speed: Int): Unit = {
    state.update(Highway.driveAlong(speed))
  }
}

The default path of the mappings is always „/“, regardless of what the method is called. The subpaths „/dillo“ and „/drive“ must be explicitly declared as Array in Scala.

A big pitfall exists in Scala 3 before version 3.6, where you can write the annotation as it appears in many Java tutorials for Spring:

@PostMapping("/dillo")

However, this code compiles to something different than in Java and sets the name of the annotation instead of the value. You‘ll notice this either when the requests don‘t work or when Spring complains about multiply declared paths.

Now we can play our game with CURL!

$ curl -X POST -d "name=Sammy" localhost:8080/dillo
"5ee5db86-8906-4a9e-a37c-543d69015455"

$ curl -X POST -d "speed=80" localhost:8080/drive

$ curl -X POST -d "name=Mona" localhost:8080/dillo
"d39f99ee-6405-4bce-8ee3-0157b1c4adcb"

$ curl -X POST -d "speed=90" localhost:8080/drive

$ curl localhost:8080/
Sammy is dead
Mona says Hi!

The names of the parameters of the two methods dillo and drive are by default also the names of the form parameters in the POST request.

Conclusion

So we were able to create a web server with Spring Boot and connect our functional application core to it. That‘s not bad at all, and in some situations it may be a good alternative to the Play Framework or other Scala libraries.

In a follow-up article, we‘ll explore whether and how we can use Spring Data to store the application state in a database.