How we ported our application from Java to Scala



How we ported our application from Java to Scala

0 0


flatMap-2014-talk


On Github elacin / flatMap-2014-talk

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);
    }

    ...
}

Mechanically convert

Source graph

  • Scala calling Java 👍

  • Java calling Scala 👎

  • start converting controllers, then continue downstack

  • keep all inter-module APIs «Java-friendly» while you can
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! :)

looks like this

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

Despringify

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