Search code examples
javagitscalaarchitecturesbt

How to efficiently branch into Java development from old Scala (sbt) project


I am currently taking over old Scala application with main goal to write new functionality in Java, but leveraging old common code. Up till now repository looked more-or-less like this:

├──common-scala-module-1
│   ├── src
│   │   ├── scala
│   │   │   ├── **/*.scala
│   │   ├── test
│   │   │   ├── **/*.scala
├──common-scala-module-2
│   ├── src
│   │   ├── scala
│   │   │   ├── **/*.scala
│   │   ├── test
│   │   │   ├── **/*.scala
├──scala-module-3
│   ├── src
│   │   ├── scala
│   │   │   ├── **/*.scala
│   │   ├── test
│   │   │   ├── **/*.scala
├──scala-module-4
│   ├── src
│   │   ├── scala
│   │   │   ├── **/*.scala
│   │   ├── test
│   │   │   ├── **/*.scala
├── build.sbt
├── .gitignore
└── (other scala files that doesn't matter for this problem)

All of those 4 produce jar modules, but the ones that really do matter for production code are from scala-module-3 and scala-module-4 (which depend on classes in both common modules). I need to start writing new code (new modules, like scala-module-3) but in Java from this point onward. I will still need however to maintain the old code if such need arises. There are two main concerns that I have and which I'd like to get architectural advice on (I wrote what I currently believe is the best path in parentheses after question):

  1. Shall I leave this repository alone and start a new one for Java modules? (I believe that makes much sense to split them: even though codebase should work just fine together if I leave it in one repo. IMO because I make new modules instead of just changing the old ones gives me a lot of reason to just add jar's from common modules as dependencies to Java code and leave old repository maintenance-only)
  2. Shall I migrate from SBT to Gradle/Maven and if so: which and why? Does answer change based on what will I do as result of question 1? (I used Maven and Gradle in all of my current projects, they are field-tested and they will help up a big time when managing builds and dependencies plus they are a standard in Java projects, so I gravitate towards Maven. Last but not least: I don't know SBT too much, which can give me additional headache)

NOTE: I do have pretty extensive Java background in multiple projects and I do know Scala basics (like that it leverages JVM technology and that SBT should work even if I'd just start putting Java code into the repository out of the blue). I am just looking for architectural advice in order to know the trade-offs and make my (and maybe someday others) life easier as this application is now leaving Scala behind (outside of bugs/maintenance).


Solution

  • Your primary problem is not to choose between Maven or Gradle.

    Your primary problem is that you most likely cannot directly use any of those dependencies in your Java code (the fact that it compiles to JVM bytecode doesn't matter because you will not write your future packages in bytecode).

    The reasons why you cannot directly use any of these dependencies are:

    1. Scala can trivially represent Java's types, whereas Java cannot represent almost anything from Scala's type system without contortions.
    2. Scala can trivially instantiate all Java's interfaces and classes, whereas Java will have a really hard time instantiating any Scala ADTs or accessing stuff on companion objects
    3. There is no metaprogramming mechanism in Java, so it cannot make use of the implicits / givens, let alone macros. Thus, exposing the givens is like screaming into an abyss, and not getting any response back, i.e. noisy & pointless.

    One reasonable thing that you might attempt:

    1. List all subprojects on which you want your java code to depend, i.e. common-scala-module-1, common-scala-module-2 etc.
    2. For each of those subprojects <P>, either create additional <P>-javaapi subproject (i.e. common-scala-module-1-javaapi, common-scala-module-2-javaapi etc.), or create an additional <P>/javaapi subpackage (i.e. common-scala-module-1/src/main/java/org/whatever/tool/javaapi)
    3. Inside of each <P>-javaapi subproject / subpackage:
      • Write a pure Java API, exposing all the functionality provided by module <P>, but written as idiomatic Java. Brace yourself for FactoryFactories and pages upon pages of repetitive generics.
      • Implement the pure Java API in Scala, using the functionality provided by <P>
      • Build the <P>-javaapi with SBT (the -javaapi is a mixed Scala/Java project, so probably better to build it with SBT)
    4. Henceforth, use the plain Java API from the -javaapi packages.

    This should work, because:

    • Any functionality that you can possibly use from Java can be expressed through Java-only types and interfaces, no reasons to leak any Scala types and implicits if they can't be used anyway.
    • Implementing Java interfaces in Scala is trivial
    • Converting all collections through scala.jdk.CollectionConverters etc. is trivial.
    • Building the mixed -javaapi packages with SBT should work fine.

    By the end, you obtain bunch of packages common-scala-module-1-javaapi ... common-scala-module-N-javaapi that are practically indistinguishable from any Java packages not only on bytecode level, but also on the level of exported types, interfaces, and documentation. The only difference is that they would have weird dependencies (but you won't notice it while using them).

    Unless your code is a tightly coupled mess that prevents you from testing common-scala-module-Ns without also modifying and rerunning all your non-common-modules, then it's probably better to create a separate repo with separate java-module-0, java-module-1 etc. and build them with Maven (or Gradle).