About Adam Warski

Adam is one of the co-founders of SoftwareMill, a company specialising in delivering customised software solutions. He is also involved in open-source projects, as a founder, lead developer or contributor to: Hibernate Envers, a Hibernate core module, which provides entity versioning/auditing capabilities; ElasticMQ, an SQS-compatible messaging server written in Scala; Veripacks, a tool to specify and verify inter-package dependencies, and others.

Using Scala traits as modules, or the “Thin Cake” Pattern

I would like to describe a pure-Scala approach to modularity that we are successfully using in a couple of our Scala projects.

But let’s start with how we do Dependency Injection (see also my other blogs). Each class can have dependencies in the form of constructor parameters, e.g.:
 
 
 
 
 
 

class WheatField
class Mill(wheatField: wheatField)
class CowPasture
class DiaryFarm(cowPasture: CowPasture)
class Bakery(mill: Mill, dairyFarm: DairyFarm)

At the “end of the world”, there is a main class which runs the application and where the whole object graph is created:

object BakeMeCake extends App {
     // creating the object graph
     lazy val wheatField = new WheatField()
     lazy val mill = new Mill(wheatField)     
     lazy val cowPasture = new CowPasture()
     lazy val diaryFarm = new DiaryFarm(cowPasture)
     lazy val bakery = new Bakery(mill, dairyFarm)

     // using the object graph
     val cake = bakery.bakeCake()
     me.eat(cake)
}

The wiring can be done manually, or e.g. using MacWire.

Note that we can do scoping using Scala constructs: a lazy val corresponds to a singleton object (in the constructed object graph), a def to a dependent-scoped object (a new instance will be created for each usage).

Thin Cake pattern

What if the object graph, and at the same time the main class, becomes large? The answer is simple: we have to break it into pieces, which will be the “modules”. Each module is a Scala trait, and contains some part of the object graph.

For example:

trait CropModule {
     lazy val wheatField = new WheatField()
     lazy val mill = new Mill(wheatField)     
} 

trait LivestockModule {
     lazy val cowPasture = new CowPasture()
     lazy val diaryFarm = new DiaryFarm(cowPasture)
}

The main object then becomes a composition of traits. This is exactly what also happens in the Cake Pattern. However here we are using only one element of it, hence the “Think Cake” Pattern name.

object BakeMeCake extends CropModule with LivestockModule {
     lazy val bakery = new Bakery(mill, dairyFarm)

     val cake = bakery.bakeCake()
     me.eat(cake) 
}

If you have ever used Google Guice, you may see a similarity: trait-modules directly correspond to Guice modules. However, here we gain the additional type-safety and compile-time checking that dependency requirements for all classes are met.

Of course, the module trait can contain more than just new object instantiations, however you have to be cautious not to put too much logic in there – at some point you probably need to extract a class. Typical code that also goes into modules is e.g. new actor creation code and setting up caches.

Dependencies

What if our trait modules have inter-module dependencies? There are two ways we can deal with that problem.

The first is abstract members. If there’s an instance of a class that is needed in our module, we can simply define it as an abstract member of the trait-module. This abstract member has to be then implemented in some other module with which our module gets composed in the end. Using a consistent naming convention helps here. The fact that all abstract dependencies are defined at some point is checked by the compiler.

The second way is composition via inheritance. If we e.g. want to create a bigger module out of three smaller modules, we can simply extend the other module-traits, and due to the way inheritance works we can use all of the objects defined there.

Putting the two methods together we get for example:

// composition via inheritance: bakery depends on crop and livestock modules
trait BakeryModule extends CropModule with LivestockModule {
     lazy val bakery = new Bakery(mill, dairyFarm)
}   

// abstract member: we need a bakery
trait CafeModule {
     lazy val espressoMachine = new EspressoMachine()
     lazy val cafe = new Cafe(bakery, espressoMachine)

     def bakery: Bakery
}

// the abstract bakery member is implemented in another module
object CafeApp extends CafeModule with BakeryModule {
     cafe.orderCoffeeAndCroissant()
}

Multiple implementations

Taking this idea a bit further, in some situations we might have trait-module-interfaces and several trait-module-implementions. The interface would contain only abstract members, and the implementations would wire the appropriate classes. If other modules depend only on the trait-module-interface, when we do the final composition we can use any implementation.

This isn’t perfect, however. The implementation must be known statically, when writing the code – we cannot dynamically decide which implementations we want to use. If we want to dynamically choose an implementation for only one trait-interface, that’s not a problem – we can use a simple “if”. But every additional combination causes an exponential increase in the cases we have to cover. For example:

trait MillModule {
     def mill: Mill
}

trait CornMillModule extends MillModule { 
     lazy val cornField = new CornField()
     lazy val mill = new CornMill(cornField)
}

trait WheatMillModule extends MillModule { 
     lazy val wheatField = new WheatField()
     lazy val mill = new WheatMill(wheatField)
}

val modules = if (config.cornPreferred) {
     new BakeryModule with CornMillModule
} else {
     new BakeryModule with WheatMillModule
}

Can it be any better?

Sure! There’s always something to improve :). One of the problems was already mentioned – you cannot choose which trait-module to use dynamically (run-time configuration).

Another area that could get improved is the relation between trait-modules and packages. A good approach is to have a single trait-module per package (or per package tree). That way you logically group code that implements some functionality in a single package, and specify how the classes that form the implementations should be used in the trait-module. But why then do you have to define both the package and trait-module? Maybe they can be merged together somehow? Increasing the role of packages is also an idea I’ve been exploring in the Veripacks project.

It may also be good to restrict the visibility of some of the defined objects. Following the “one public class per package” rule, here we might have “one public object per trait-module”. However, if we are creating bigger trait-modules out of smaller ones, the bigger module has no way to restrict the visibility of the objects in the module it composes of. In fact, the smaller modules would have to know the maximum scope of their visibility and use an appropriate private[package name] modifier (supposing the bigger module is in a parent package).

Summing up

Overall, we found this solution to be a simple, clear way to structure our code and create the object graph. It uses only native Scala constructs, does not depend on any frameworks or libraries, and provides compile-time checking that everything is defined properly.

Bon Appetit!
 

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


+ six = 8



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close