The myth of using Scala as a better Java
2017-02-16
When people talk about their experience with Scala, they often say that it is possible to use Scala as a better Java. And indeed, many companies, especially the ones that adopted Scala around 2008-2009, didn't want to give up the familiar tooling and simply integrated Scala into existing workflows based on Maven. At that time, calling Scala an improved version of Java was questionable but at least justifiable. However, it's no longer the case. For the most part, contemporary Scala shops don't use Maven as a build tool, don't use Spring as a DI container and rarely, if ever, resort to classical design patterns. What do they use then?
Dependency injection
Arguably the most popular Scala Web framework is Play, and when it just came out, it didn't provide any infrastructure for doing proper DI. It was possible to build your own solution based on a third-party library, but nothing was enforced. The 2.4 version shipped with Google Guice as a compromise DI solution. Not everyone in the Scala world was very eager to bring back Java annotations, and luckily, this wasn't necessary. With the Play framework, it was always possible to replace Guice with another runtime solution or go a completely different route and use a so-called compile-time dependency injection.
With compile-time DI, you make service classes accept lower level dependencies as constructor parameters. In Scala, constructor parameters are accessible from regular methods, so most of the time, these services could be completely stateless. This approach is also advocated by several Java (and .NET) experts, but it's by no means mainstream. Interestingly, Scala developers have an advantage over Java/C# users, because they can significantly simplify the implementation by combining a macro-based library with lazy definitions:
// trait AppComponents
lazy val userDao = wire[UserDao]
lazy val sessionDao = wire[SessionDao]
lazy val userService = wire[UserService]
lazy val authService = wire[AuthService]
The above example (taken from my "Practical Event Sourcing with Scala") uses a function called wire
that comes from the MacWire library. During the compilation phase, this function will analyze each class and invoke their constructors while passing already initialized services as arguments. In addition, making these values lazy ensures that the compiler, not the developer, will take care of the initialization order.
Not surprisingly, this approach has become very popular in recent years and made the Play team consider promoting it as the default in the upcoming version of the framework.
Database access
Another typical aspect of development is interacting with the database. When Java first came out, it was great to see a language with sane API for querying databases available out of the box. However, using plain JDBC involves writing so much boilerplate that hardly anybody is doing it these days. Instead, the mainstream Java approach is to use an ORM.
When using an ORM, you usually create domain classes and annotate them to describe how the classes map to a database schema. In return, the ORM provides you with simple methods for persisting and retrieving domain objects while performing the object-relational mapping automatically along the way. This approach certainly looks great in theory, but in practice, it leads to numerous problems. Some people from the Java community pointed out that writing plain SQL manually is actually much better than relying on an ORM to generate necessary statements. The most prominent example is a library called jOOQ, which allows developers to write type-safe SQL code using Java methods:
create.selectFrom(BOOK)
.where(BOOK.PUBLISHED_IN.eq(2011))
.orderBy(BOOK.TITLE)
However, this approach is not that common in Java, and this particular library has very few quality alternatives. Most people continue using ORMs.
Interestingly, ORMs have never really taken off in Scala. Scala beginners like to make a point that the possibility of using Hibernate is one of the selling points of the language. Yet, nobody seems to need it. The most popular Scala libraries for dealing with databases are ScalikeJDBC, Slick, Quill. All of them are more similar to jOOQ rather than Hibernate. Again, Scala users have an advantage over their Java colleagues, because most of these libraries rely heavily on Scala features (macros, implicit parameters, string interpolation) not present in Java.
Consider the following example that uses ScalikeJDBC:
// class UserDao
def getUser(userCode: String): Try[Option[User]] = Try {
NamedDB('auth).readOnly { implicit session =>
sql"select * from users where user_code = $userCode".
map(User.fromRS).headOption().apply()
}
}
The code may appear as if it's vulnerable to SQL injection, but actually it's not: the SQL string gets converted to a type-safe PreparedStatement
behind the scenes. The getUser
method also uses some Scala features - namely Try
and Option
- to better communicate the expected result type. I already explained how using for expressions for combining monadic structures makes your code clearer (see this blog post for details), but the important thing here is that nobody does anything like this in Java, simply because the language doesn't support it.
JSON serialization
The most popular library for serializing and parsing JSON in Java is Jackson. Jackson is included as a low-level dependency in many (probably, the most of) JSON-related libraries and used universally by virtually everyone. Jackson uses the reflection API for mapping fields, but the default behaviour can be adjusted via annotations:
public class Name {
@JsonProperty("firstName")
public String _first_name;
}
Since everything in the Java world revolves around the concept of JavaBeans, by default, Jackson assumes that your classes contain private mutable fields and getters/setters for accessing/mutating them. Interestingly, the Jackson umbrella also contains a subproject called jackson-module-scala
. It definitely has its users, but my experience shows that most Scala developers prefer using something else.
Scala famously has several actively developed JSON libraries, and most of them are not based on reflection. Let's take a look at one of the most popular ones - play-json
.
In Scala, you don't usually make your data classes mutable. Instead, you create a case class
and make it immutable:
case class Tag(id: UUID, text: String)
How can you serialize it to JSON? That's very simple! Just add an implicit value of type Writes
to the companion object:
object Tag {
implicit val writes = Json.writes[Tag]
}
And that's it! You don't need to implement special marker interfaces or register your class anywhere. This is basically type classes in action, and surprisingly, you don't really need to know about this fact to use them (you can always check my previous post, though). Another interesting thing is that Json.writes
helper method is macro-based rather than reflection-based. And again, Java developers cannot use this library, because Java doesn't support type classes and macros.
Conclusions
Let's wrap things up with a simple conclusion. Scala developers tend not to use Spring for doing dependency injection, Hibernate for database access or reflection-based libraries for JSON serialization. Instead, they usually rely on solutions which are based on specific Scala features not existing in Java. Consequently, Scala is not a better Java, but a very distinct language with its own ecosystem, best practices and approaches. If you encounter a person who still believes in a "better Java" myth, please send them this article. If this doesn't change their mindset, I don't know what else will.
Finally, if you want to learn Scala in a practical way, check out my book bundle on Leanpub.