Our journey so far relying on Scala from Monolith to Micro-services to build a sustainable fashion consumption at StyleTheory.

Scala

“Why Scala?” is the most common question people asked when they talked to us about our tech stack.

“How do you beat Go’s fast compile time, not having to deal with JVM cold start, and even better smaller container size?” “Kotlin is more sexy now, and you do backend with Kotlin too!” “Serverless is the future” “Javascript man, frontend to backend.”

Well the general “bullshit” answer i’ll give is that languages are just tools, and in the era of micro-services, any general purpose language with adequate framework support will do.

The real answer is quite a happenstance really. The choice for Scala started because i started off with StyleTheory freelancing and the tools that allows me to go fast at that time was Scala and Play! Framework, hence the reason why the language was chosen in the first place.

As we hire more Backend engineers; them coming from diverse background of Python, Ruby and PHP engineers; we stayed with the choice by the end of the day. One PHP engineers remarked on how secure he felt when everything compiles, and the language provides great sense of predictability when you code with immutability in mind; in which Scala does a great job of helping.

If programming languages has it’s application index (Apdex), using Scala pre-late 2018 was quite painful particularly if you are writing a monolith. You would be tolerating two things: compile time and IDE support. In 2019, writing Scala and using it in production is a bliss. You get a very powerful language, mountains of great libraries and improved development tools.

1. Visual Studio Code as your IDE

You hated an IDE that takes time to load and consumes tons of memory? Fret not! VSCode to the rescue!

The Scala support came in last year, but with improvements over time it has finally been able to compete with Jetbrain’s offering.

The only missing crucial feature is symbol renaming, but i’m sure it’ll come soon enough. In the mean time, compared to Intellij and Scala Plugin, Metals and Bloop in my opinion is much better in terms of build imports which helps to make it easier getting started in the first place.

I, myself have been using this to write production codes and the experience has been satisfying. Using Bloop to run tests are much faster, and produce cleaner output without having to tweak some SBT settings.

2. Micro-service framework of choice: Lagom

One might rarely heard of the framework, and if you have not heard of it let me introduce this for you: it is an opinionated framework to build CQRS and Event-Sourced application, built on top of Akka and Play.

Even though it is built for specific architecture, you can rely on it extremely well for building general purpose CRUD applications with no issue. If you needed to write Event Sourced applications, it helps you to develop it by providing Kafka and Cassandra embedded for testing out of the box. Other than that, it has out of the box support for circuit breaker, gRPC, docker build config and service discovery.

What i like the most is the support for running it on top of Kubernetes with Akka Service Discovery and Akka Management. The support allows you to run either a stateful or stateless Lagom services easily operated on Kubernetes. We are running our Inventory service specifically with Event Sourcing and thank goodness clustering Akka on top of Kubernetes is no longer a mystery.

On top of that, if you rely on Datadog or New Relic for APM, they all have out of the box support for Lagom too!

3. Scala 2.12 and 2.13

What Scala 2.12 brings to the table is a much faster compile time, with 2.13 (along with collection-api revamp) continuing that tradition which improves it over 10% compared to 2.12. This truly helps with the ongoing trend of going micro-service, which means writing small services with Scala is getting more and more comfortable. I can actually assure you that the compile time experience is satisfactory (plus running tests too).

Scala 2.13 would then be binary compatible with the upcoming 2.14, which helps bridge the migration to Scala 3 (dotty). Dotty is particularly exciting since it will bring Scala closer to it’s functional heart, and hopefully reduce the division within the Scala community.

I haven’t had the chance to upgrade to 2.13 yet (due to some libraries that hasn’t published 2.13 artifacts), but i would imagine the migration is as seamless as updating the `scalaVersion` key in build.sbt to 2.13.

4. Better performance with GraalVM

GraalVM is probably one of the best thing happening in the Java land, and better yet Graal’s JIT’s partial escape analysis works great for Scala code. This results in almost 1.2–1.3x improvement over JavaHotSpot VM. The speedup doesn’t just come to running a Scala service, it also helps compiling Scala code faster.

sbt-native-packager setting for docker base image

If you rely on sbt-native-packager to build your docker image, using GraalVM is as simple as change the `dockerBaseImage` config.

5. Native Image with GraalVM and SBT Native Packager

If you are fed up with the JVM cold start, then native image is your answer. SBT Native Packager recently recently have added support to generate native-images right from your favourite Scala build tools. If your normal cold start could take 5–6 seconds, imagine having your services start with just 0.033 sec!

If your service depends on Akka, you might need certain library to help you generate a native image. There is a sample project that succeeded in doing this.

6. Slick for Database

It is not to everyone’s taste to use Slick, but i would argue it is much more beneficial to rely on an extremely type-safe FRM (functional relational mapping) such as Slick. If you happen to use Postgres, there is an extension library for Slick that provides mapping for a lot of Postgres data type like Geometry, Range or LTree.

// sample slick table definition class CustomerTable(tag: Tag)

extends Table[Customer](tag, "customers") { def id: Rep[UUID] = column[UUID]("id", O.PrimaryKey, O.AutoInc)

def categories: Rep[List[Int]] = column[List[Int]]("categories")

def properties: Rep[JsValue] = column[JsValue]("properties") } // using the table

val customerTable = TableQuery[CustomerTable] // pretend you have an instance of db lying around somewhere

db.run(

customerTable

.filter(id === uuid)

.map(_.categories)

.result.headOption

)

// returns an Option[Customer]

// compiles to SELECT categories FROM customers where id = ?

The idea is to allow us to query the database with more functional syntax such as filter or map. It helps you to write type-safe queries and help prevent minute mistakes like passing on the wrong type when writing the query.

Slick also provide a codegen which helps you generate the table definition for you, in case it gets tedious to write it.

Slick is not an ORM, which means you can’t use it like when you are using an ORM. It is much closer to SQL itself since SQL is by nature declarative, while an ORM tries to make use of your tables as if it is your in-memory domain model, which might be problematic because of the object-relational impedance mismatch.

Depending on the type of architecture you choose, the way you store your data might differ. If you try to apply a DDD friendly architecture such as CQRS and Event Sourcing you might benefit from using an FRM because of a strong separation between your domain model and your database table. You would most likely use simple queries like select, insert or update without much joins due to the clear separation of concern as a result of the choice of architecture.

You are pushing Java to be better

Honestly without the adoption of more alternative JVM languages like Scala or Kotlin, i personally doubt Java will improve as fast. The alternative languages provide great ground for experimenting features that will soon be adopted by Java. Adopting Scala to me means pushing the entire ecosystem forward.

Using it in a Startup

If you are wondering whether Scala allows you iterate fast, the answer gets more complicated. Are the talent pools ready? How long do someone needs to spend to learn the language?

Languages are tools, like hammers, or screwdrivers. Start with what you know best! It doesn’t matter if it happens to be Scala, Java, or Golang; to me the answer is to have always a great on-boarding process. In our case, all of the new engineers are able to send their first pull request within the first week of work, including the juniors!

Sample on-boarding material

Building a startup doesn’t mean we neglect doing on-boarding for the reason of moving fast ahead. It is probably one of the most crucial element when we are hiring someone since on-boarding process gives the lasting impression for new comers.

All in all, it is quite a roller coster of a journey going along with Scala and seeing how it kept on being improved over time has given me high confident that the language is going forward to a bright future.