Spring Framework and the Cake pattern

Posted by Jan Machacek

Find me on:

07-Feb-2012 11:51:00

Even though the Spring Framework is (amongst many other things) a dependency injection container, there is still some scope to use the Scala Cake pattern in your Spring code. I am going to talk about the details and the background tomorrow in my Spring in Scala talk tomorrow at Skills Matter; but here are the most important details and motivation.


In a Spring application, we let the framework manage components that expose some API. The components are usually interface-implementation pair; the API is, of course, the methods defined in the interface. Spring Framework then uses metadata we provide (either in XML or in annotations) to instantiate the components and wire up the component together. In other words, we very rarely write code that instantiates or looks up the components, we leave that to the Spring Framework.

If you are using Scala to implement your Spring components, you may be wanting to use the mixin composition that Scala provides in your Spring beans. Let's build an application that can register a user; we want to configure the way in which we hash (and salt!) the passwords and the way in which we notify the users of a successful registration. In Java/Spring, we will usually write:

public interface PasswordCodec {
  String encode(String plain);
}
public class Sha1PasswordCodec implements PasswordCodec {
  public String encode(String plain) {...}
}
public class PlainPasswordCodec implements PasswordCodec {
  public String encode(String plain) {...}
}

public interface Notifier {
  void registered(User user);
}
public class EmailNotifier implements Notifier {
  public void registered(User user) {...}
}
public class SmsNotifier implements Notifier {
  public void registered(User user) {...}
}

The UserService would then have the instances (configured at runtime) injected into it by the Spring Framework (see Figure 1).

Figure 1. Java-like composition

javacomp

This is a good start, but the Sha1PasswordCodec, PlainPasswordCodec, EmailNotifier and SmsNotifier are "closed"--that is, we cannot mixin their functions into another component in our system. We can only use the object-oriented delegation pattern. Scala offers the Cake pattern, which takes advantage of the self type annotation to enforce mixing in of other components. Think of this as compile-time dependency injection, where the dependency is not an instance, but a piece of functionality. So, turning this into Scala, we would change the components from classes into traits; viz Figure 2.

Figure 2. Scala composition

scalacomp

Turning this into Scala code, we'll make the substitution and use the self type annotation on the UserService:

trait PasswordCodec {
  def encode(plainText: String): String
}
trait Sha1PasswordCodec
  extends PasswordCodec {
  def encode(plainText: String) = ...
}
trait PlainPasswordCodec
  extends PasswordCodec {
  def encode(plainText: String) = ...
}

trait Notifier {
  def registered(user: User)
}
trait EmailNotifier
  extends Notifier {
  def registered(user: User) { /* ... */ }
}
trait SmsNotifier extends Notifier {
  def registered(user: User) { /* ... */ }
}

class UserService (private val sessionFactory: SessionFactory) {
  this: PasswordCodec with Notifier =>

  @Transactional
  def register(user: User) {
    user.password = encode(user.password)
    registered(user)
    sessionFactory.getCurrentSession.saveOrUpdate(user)
  }
}

The line this: PasswordCodec with Notifier => says that the instance of the UserService must mix-in the PasswordCodec and Notifier traits. Furthermore, the various password codecs and notifiers remain traits, which means that we can compose the functions contained within them (the registered(User): () and encode(String): String) into any other component in our system using Scala's inheritance.
So, given the self type annotation means that we cannot just write new UserService:

new UserService(sessionFactory)              // doesn't compile
new UserService(sessionFactory) extends
  PlainPasswordCodec                         // doesn't compile
new UserService(sessionFactory) extends
  SmsNotifier                                // doesn't compile

new UserService(sessionFactory) extends
  PlainPasswordCodec with SmsNotifier        // OK
new UserService(sessionFactory) extends
  Sha1PasswordCodec with EmailNotifier       // OK

So, it's looking good. I have more reusable components (because they are traits, so they can be mixed-in further) and I have expressed the composition dependency of the UserService. But we're in the Spring application, remember? Now can I construct the UserService bean? Spring Scala to the rescue!

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:scala="http://www.springframework.org/schema/scala"
  xsi:schemaLocation="...">

  <context:load-time-weaver aspectj-weaving="on" />
  <context:component-scan base-package="org.cakesolutions.scala.services"/>

  <scala:bean id="userService" class="UserService">
    <scala:constructor-arg ref="sessionFactory"/>
    <scala:with trait="Sha1PasswordCodec"
                   if="#{environment['profile'] == 'live'}"/>
    <scala:with trait="PlainPassowordCodec"
                   if="#{environment['profile'] != 'live'}"/>
    <scala:with trait="EmailNotifier" />
  </scala:bean>

</beans>

Excellent! Not only can I mixin the required components, the Spring Scala namespace also gives me the control over when the dependencies are mixed-in. In the example above, the ShaPasswordCodec is mixed in only when the SPEL expression environment['profile'] == 'live' is true (presumably, this will be on the live environment). So, with the Spring Scala namespace, you can keep using Scala's mixin inheritance, consider isolating the functionality you need in traits, thus giving you the opportunity to compose them into new components.

Subscribe to Email Updates