Developing Modern Applications with Scala: Build with SBT
This article is part of our Academy Course titled Developing Modern Applications with Scala.
In this course, we provide a framework and toolset so that you can develop modern Scala applications. We cover a wide range of topics, from SBT build and reactive applications, to testing and database acceess. With our straightforward tutorials, you will be able to get your own projects up and running in minimum time. Check it out here!
1. Introduction
For many experienced Java developers, Scala programming language is not a stranger. It’s been around for quite a while now (officially, since first public release in 2004) and gained quite a lot of traction in the recent years.
There are many reasons why one is going to pick Scala over Java, Clojure, Groovy, Kotlin, Ceylon, … and we are not going to discuss that in this tutorial. However, what we are going to talk about is the ecosystem of tools, frameworks and libraries which Scala community has developed over the years to provide a native Scala experience for the developers.
Table Of Contents
We are going to start from the basics, like build tools and testing frameworks, talking about principles of reactive applications, accessing data storages, concurrency and parallelism, and finish up by outlining the typical choices in building console applications, web services and full-fledged web applications.
2. Build Tools: Solid Foundation of Every Project
One of the first things every software project is facing at a quite early stage is how it is going to be built and manage its dependencies. The choice of tools ranges from custom made shell scripts, make, XML-based Apache Ant, Apache Maven, Apache Ivy, to really sophisticated ones like for example Gradle and Apache Buildr.
3. SBT: (not so) Simple Build Tool
It comes as no surprise that Scala community has own view on the projects build and dependency management and SBT, or simple build tool, is the direct confirmation of that. SBT differs from any other build tool out there, first of all because it uses Scala language for describing the build definitions.
In its current version 0.13.11, SBT is still on its undergoing journey to 1.0 release (which is going to happen soon, hopefully). The simplest way to install SBT is to download it as a platform-independent archive, extract it into some location and include this location into your operating system PATH environment variable.
Please notice that appropriate Java version (at least Java 7 but Java 8 is the recommended distribution these days) should be installed before.
Although S in SBT stays for “simple”, for many developers it does not sound like it is. May be because of the way how project builds are being described (essentially, key/value pairs), or may be because there are multiple ways of creating build definitions (using .sbt and .scala files). Nonetheless, in this section we are going to demystify some SBT concepts, hopefully making this powerful tool really simple to understand and use.
To be noted here, SBT is really very capable and feature-rich tool, worth of a complete tutorial to be written. We are going to talk about some basic concepts so to let us get started quickly however do not hesitate to look at the comprehensive documentation available online any time.
4. Project Structure
Most of the build tools try to impose some conventions regarding how the project layout should look like. SBT does not introduce yet another one but instead follows the same project layout conventions as Apache Maven, for example:
<project-name> | - project; | - src | - main | | - scala | | - resources | - test | - scala | - resources
The only stranger in this layout is the project subfolder. It is specific to SBT and in many cases contains only two files:
- properties with desired SBT version, for example:
sbt.version=0.13.11
- sbt with the list of additional SBT plugins, for example:
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
Although plugins.sbt is still widely used, more recent SBT versions are lean towards using a little bit different approach as described in the Using Plugins section of the SBT documentation.
5. Build Definitions
As we already briefly touched upon, SBT support multiple ways of creating build definitions: using .sbt files and .scala files. Furthermore, with regards to .sbt files, there are so called bare .sbt files which should not be used anymore but we are going to discuss them a bit as we may encounter them in the wild quite often.
Essentially, no matter what approach you are going to use, at the end the result is always the same: provide the description of each project as a set of key / value pairs, which are often called settings. Along with settings, SBT has very important notions of tasks (for example compile, package, run) and configurations (for example Compile, Runtime, Test). With that being said, depending on task and/or configuration, every setting may have different value so settings (or more precisely, setting keys) are said to be associated with some scope (or just scoped). Under the hood, it is certainly more complex than it sounds but thinking about the build definition in this way is a good starting point. For the curious ones, .sbt build definition and Scopes sections of SBT online documentation go much deeper into details.
In the next couple of sections we are going to look at the different SBT build definitions using just simplest single-module projects as well as more complex multi-module projects. Both build definition styles, using .sbt or .scala files, are going to be discussed so we could pick the one closer to your heart.
6. Single-Module Projects
Projects which consist of a single module are extremely easy to build using just a few lines in SBT build definition build.sbt file, for example:
lazy val main = (project in file(".")) .settings( name := "single-module-sbt", version := "0.0.1-SNAPSHOT", scalaVersion := "2.11.8", libraryDependencies ++= Seq( "ch.qos.logback" % "logback-classic" % "1.1.2" ), resolvers += "Typesafe" at "https://repo.typesafe.com/typesafe/releases/", mainClass in (Compile, run) := Some("com.javacodegeeks.single.App") )
That basically is the complete build definition! In the nutshell, we just referred to the current directory as a project:
lazy val main = (project in file("."))
And defined a couple named pairs (key / value pairs or settings), like name, version, scalaVersion, libraryDependencies, resolvers. In this regards mainClass stands separately as it additionally includes configuration (Compile) and task (run) scopes.
As of now, this is the recommended way of using SBT with Scala projects (and projects written in other languages which SBT supports). Using .scala and bare .sbt styles are considered deprecated in favor of this one. Nonetheless, let us take a look on the examples of both, starting from .scala, as there are chances one or more of your current projects may already use them.
The traditional way the .scala based builds are being organized is to have Build.scala file in the project folder as an entry point. For example, the same build definition is going to look a little bit differently but still close enough:
import sbt._ import Keys._ object ProjectBuild extends Build { override val settings = super.settings ++ Seq( name := "single-module-sbt", version := "0.0.1-SNAPSHOT", scalaVersion := "2.11.8", libraryDependencies ++= Seq( "ch.qos.logback" % "logback-classic" % "1.1.2" ), resolvers += "Typesafe" at "https://repo.typesafe.com/typesafe/releases/" ) lazy val main = Project( id = "single-module-scala", base = file("."), settings = Project.defaultSettings ++ Seq( mainClass in (Compile, run) := Some("com.javacodegeeks.single.App") ) ) }
Comparing to the previous .sbt version, this build definition looks considerably more verbose, with quite a bit of code being written. Surely, it is still readable but the benefits of the recommend approach become obvious. To read more about .scala build definitions please take a look at the official documentation.
To finish up, let us take a look at one of the simplest build definition possible, using so called these days just bare .sbt file.
name := "bare-module-sbt" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.8" libraryDependencies ++= Seq( "ch.qos.logback" % "logback-classic" % "1.1.2" ) resolvers += "Typesafe" at "https://repo.typesafe.com/typesafe/releases/" mainClass in (Compile, run) := Some("com.javacodegeeks.bare.App")
It is still the same build.sbt, but defined using pure settings based approach (please notice, the new lines between each setting definition are mandatory). This build definition style is explained in the official documentation as well.
As it was already mentioned, .scala and bare .sbt build definitions are not recommended for use anymore so we are not going to get back to them in the tutorial.
7. Multi-Module Projects
Most real-world projects are organized in a set of modules to encourage code reuse and proper boundaries. Logically, in this case many build settings, tasks and properties could be shared between most (or even all) modules in the project and the choice of the right build tool becomes increasingly important.
SBT support multi-module project very naturally, making it no brainer to add new modules or maintain quite complex build definitions. Let us come up with a sample multi-module project which consists of 3 modules: core-module, api-module and impl-module.
<project-name> | - project | - core-module | | - src | | - main | … | - api-module | | - src | | - main | … | - impl-module | - src | - main …
There are quite a few ways to organize your build but in our case the project’s root folder is the only one which contains project folder and build.sbt file.
lazy val defaults = Seq( version := "0.0.1-SNAPSHOT", scalaVersion := "2.11.8", resolvers += "Typesafe" at "https://repo.typesafe.com/typesafe/releases/" ) lazy val core = (project in file("core-module")) .settings(defaults: _*) .settings( name := "core-module" ) lazy val api = (project in file("api-module")) .settings(defaults: _*) .settings( name := "api-module" ) lazy val impl = (project in file("impl-module")) .settings(defaults: _*) .settings( name := "impl-module", libraryDependencies ++= Seq( "ch.qos.logback" % "logback-classic" % "1.1.2" ), mainClass in (Compile, run) := Some("com.javacodegeeks.multi.App") ) .dependsOn(core) .dependsOn(api) lazy val main = (project in file(".")) .aggregate(core, api, impl)
Essentially, multi-module (or multi-project) build definition is just a set of individual projects, sharing some common settings (for example lazy val defaults) and declaring dependencies on each other using dependsOn:
lazy val impl = (project in file("impl-module")) … .dependsOn(core) .dependsOn(api)
SBT is taking care of everything else (like constructing dependency graph and figuring out the proper build sequence). For the curious ones, it is quite well explained in the official documentation section.
8. Migrating from Maven
Arguably, these days Apache Maven is still one of the most popular build tools in Java ecosystem. Likely, SBT provides an easy migration path from Apache Maven using externalPom configuration (assuming there is a pom.xml file in the along with build.sbt), for example:
lazy val main = (project in file(".")) .settings( organization := "com.javacodegeeks", name := "pom-module-sbt", version := "0.0.1-SNAPSHOT", scalaVersion := "2.11.8", mainClass in (Compile, run) := Some("com.javacodegeeks.maven.App"), externalPom() )
It is important to mention that externalPom has limited capabilities and imports dependencies only. For more in depth details please take a look at the official documentation.
9. Interactivity at Heart
One of the absolutely astonishing capabilities of SBT is interactivity though feature-rich shell. It is as simple as typing sbt from the root folder of your project.
$ sbt >
SBT let you to interact with your build definitions live, inspecting various settings and invoking arbitrary tasks. Not to forget the integration with Scala console thought sbt console command (or just console with interactive shell).
$ sbt console [info] Starting scala interpreter... Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala>
In the next sections of the tutorial we are going to use these SBT superpowers a lot, hopefully changing your mind about the whole concept of what build tool should be.
10. Integrations with IDEs
Last but not least, there is another very important subject to cover of how SBT integrates with popular Java IDEs. It is surprising how important such integrations are for developers in order to quickly get started with their projects.
SBT is very extensible due to its own plugins mechanism. More often than not the integrations with most popular Java IDEs are done through dedicated plugins, for example:
- sbteclipse is a plugin for SBT to create Eclipse project definitions
- nbsbt is a plugin for SBT to create Netbeans project definition
- ensime-sbt is a plugin for SBT to create Ensime project definition
Adding plugins to build definition is as simple as adding a couple of lines into plugin.sbt file in the project subfolder, for example in case of sbteclipse :
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
Consequently, the plugin functionality is accessible through the respective task (or tasks) from the command line (or from the interactive shell), for example for sbteclipse it looks like that:
$ sbt "eclipse with-source=true with-javadoc=true"
It is worth to mention that some IDEs like JetBrains IntelliJ IDEA do provide superior SBT through own plugins ecosystem.
11. Conclusions
SBT is very powerful build tool which, when used accordingly, can significantly boost your productivity. This section talked about very basic concepts but mostly every other part of the tutorial is going to refer to some new SBT features (or just rely on the ones we already know).
12. What’s next
In the next section of the tutorial we are going to talk about different testing frameworks adopted by Scala development community. We are also going to see in action how SBT could be used to run individual tests or test suites and watch for changes.
A similar tool exists for Java (build definition in Java language) as powerful yet simpler. http://project.jerkar.org