How we ported our application from Java to Scala
Øyvind Raddum Berg
@olvindberg
Penger.no
Penger.no - Early Timeline
autumn 2010
start of mortgage
project (in Java)
autumn 2011
start of insurance project (in Scala)
Penger.no is a site for helping users take control of their private finances, primarily through comparing offers from banks and insurance companies. think moneysupermarket.com
For the norwegians here, penger.no is owned by finn.no
history lesson, will keep as short as possible, will try to keep a timeline here, summarized at the end
Java project, Situation early 2012...
- code base growing quickly
- unhappy with technology
will give a short overview, technical reasons why we wanted to do something
two main observations
unhappy: wont complain too much, should be well known.
codebase size and complexity growing quickly
initial 18 months of project (jan 2011-june 2012)
two main things: small project, and size increasing
obviously not a perfect metric, but says something
projected LOC now ~60k+
never take functionality away
scala code here are two internal apps
Unhappy with technology
- limited/leaky abstractions
- want to understand whats going on
- runtime failures
- debugging
some things java culture some things technical, Framework hell
abstractions -> hibernate has spent 300k lines abstracting away sql, still need to know sql
often need to dig deep to find correct method to override
story: twisted mind - execution flow by exceptions (spring mvc)
understand -> i want to understand my dependencies! death by layers. (spring, threadlocals) evade lexical scoping, cglib rewrites bytecode deploy-time
runtime failures -> reflection, annotations
might not be a perfect developer, so this happens
story: spring wired session scope to app scope
these are good reasons for adopting scala...
Situation
- scala expertise within team
- insurance app originally scala
- backoffice app already rewritten
- mortgage app very important for us
i come in december 2012, hired partly for scala
first task was rewriting backoffice app
of course there was also...this other project
other team had all the fun
unbearable lunch
that leads me to the main reason for the rewrite...
Envy
primary motivating factor
Wanted to optimize for:
- peace of mind/trust in code
- similar technology between projects
- developer happiness
Porting strategy:
1
refactor Java codebase
2
introduce optional semantics
3
mechanically convert Java to Scala
4
Java collections
=> Scala collections
checked exceptions
=> Either/Try
5
Hibernate
=> Slick
Spring
=> cake pattern
6
Spring MVC/JSP
=> Unfiltered ++
in this order
of course, some of them happened simultaneously
on a touch a file-basis
Refactor Java codebase
- limit/contain mutability
- avoid multiple returns
- short, static functions
- composition over inheritance
- value classes
mostly java best practices now anyway
not organized, just general maintenance direction
collections: now would embrace java 8 stream api or guava, we relied on Collections.unmodifiableList
Optional semantics
- compile-time guarantees and easy porting, not elegance
- today: would have used java.util.Optional<T>
public class MyOption<T> implements Iterable<T> {
private final T value;
public static <G> MyOption<G> option(final G value) {
return new MyOption<G>(value);
}
...
}
Source graph
got this one from others' experience
java friendly is mostly easy, frameworks you use might mandate java collections
sbt supports compile order flag CompileOrder.JavaThenScala
IntelliJ <3
we loved this
an enabler for actually doing the conversion
enable converting heaps of code in one go
enable «prototyping» to check what should be refactored
Example
public class Foo<T> {
private final T param;
public Foo(T param) {
this.param = param;
}
}
automatically converted (doesnt compile, in this case):
class Foo {
def this(param: T) {
this()
this.param = param
}
private final val param: T = null
}
what you probably want:
case class Foo[T](param: T)
one pass after to clean up
doesnt understand everything
problems with: generics, multiple constructors, finality etc
doesnt matter! :)
Porting collections
easier than expected
had no problems with introducing the odd Any when neccessary
converting between hierarchies
explicitly (preferred)
import scala.collection.JavaConverters._
val javaMap: util.Map[String, Int] =
immutable.Map("foo" -> 2, "bar" -> 42).asJava
implicitly
import scala.collection.JavaConversions._
val javaMap: util.Map[String, Int] =
immutable.Map("foo" -> 2, "bar" -> 42)
on purpose didnt look into performance, assumed the worst, not neccessary
kept prefixes for coexisting
checked exceptions => Either[A, B] or Try[T]
- rewriting exposes inconsistencies
- often far reaching changes
confession. i preferred checked exceptions, was initially upset with scala
much effort spent on this
inconsistencies within a function, where parts of it would effectively have type (T or Exception)
need to homogenize parts
often far reaching consequences, Eithers will typically show up in far more places than does throws exception
also easy to continue, because you want to put things in for comprehensions
good opportunity to introduce semantically richer error types:
public Provider providerForApp(Application a,
List<Provider> ps)
throws Exception1, Exception2
def providerForApp(a: Application,
ps: Seq[Provider]): Either[Seq[Refusal], Provider]
dawned on me that we didnt need exceptions, could provide better info
move logging, error handling, etc out of business logic
</hibernate>
the part i was most passionate about.
also where we had the most discussions
also considered, believe it or not, spring jdbc, or writing wrapper around plain jdbc
scalaquery was somewhat immature
- difficult to figure out DSL
- encountered a few weird bugs in SQL generation
- technical limitations leaked over to domain types
- stuck with it because of promise and because it brought sanity.
- slick 1 was a relief
felt we might have started to use it a bit too early, did consider it a gamble
somewhat lucky, because we had relatively easy queries
only opaque/indirect part of our stack - sql queries generated
slick 2 might have fixed more, havent had time to look into it
Technical limitations leaked over to domain types
- catch 22 made us to split up database tables
- Option[Long] id fields in case classes for auto-increment fields
22 not necessarily bad in itself, but hated that it felt forced
autoincrement keys in the intersection between explicit and implicit??
feels beautiful with absolute control, but a bit cumbersome
Sanity/sleep well at night
@Transactional
public void saveThingie(final Thingie t) {
sessionFactory.getCurrentSession().save(t);
}
def saveThingie(t: Thingie)(implicit s: Session) =
ThingieTable insert t
one of my favourite things with the whole conversion
got rid of the horrible, horrible @Transactional that i never understood
granted never did really complicated things, no nested tx
Replacement functionality
- spring comes with... everything
- most functionality we need exists in scala stdlib, or is easy to write yourself
- took a few other dependencies, like commons-fileupload, and commons-email
...and almost nothing we need
app wiring
- started rewiring components early because of slick
- two apps in one, with somewhat intertwining dependencies
-
ugh...
this gave us a few small headaches
Have spring do the ultimate wiring
Share config and database connection
@Configuration
class SpringBeans {
@Bean @Autowired
def concreteApp(ds: javax.sql.DataSource, cfg: Config): ConcreteApp = {
new ConcreteApp with DataSourceDbConnectionComponent {
lazy val datasource = ds
lazy val conf = cfg
}
}
}
some process to get there, started by wiring up scala components as spring beans
Spring MVC/JSP
- by far biggest challenge
- unfiltered, argonaut
- wrote a toolkit for form handling
- wrote minimalistic templating engine
- two months of dedicated time
Product owners
- might not be as enthusiastic
- sell them something else
we sold super quick round trip for changing forms, new/shared design among projects, happy developers
Porting timeline
summer 2012
Started refactoring Java code
november 2012
maven => sbt
december 2012
Started porting to Scala
april 2013
javax.validation
june 2013
Hibernate
june 2013
Spring in service layer
january 2014
Spring MVC/JSP
Time usage
-
six months of preparatory refactoring
-
one year on and off porting
-
no dedicated time until december 2013
-
two months stop-the-world development to code new frontend,
rewrite http layer and wrap up porting effort
Codebase evolution
june 2012 - april 2014
Conclusion
- 👍 developer happiness
- 👍 peace of mind/trust in code
- 👍 similar technology between projects