code-small.jpg

Cake Team Blogs

Dependency injection vs. Cake pattern

Posted by Jan Machacek

Find me on:

15/12/11 16:09

Typically, the argument comes down to is Spring Framework better than Play Framework?, is Lift better than Spring Framework?, is Grails better than everything else?. In this post, we will explore the world of contemporary JEE applications, paying extra attention to the usual DI frameworks and frameworks that implement the Cake pattern.

For more detailed discussion of the Cake pattern, see Mark's Cake pattern in depth

Before we rush into condemning one or the other, we feel that what you need to think about what kind of application you're writing. We'll go as far as to say that if you need JMS, JMX, scheduling, WebFlow, ..., then you should stick with the Spring Framework. By all means implement the components that make up your Spring application in Scala! If these JEE dinosaurs do not come anywhere near your application, then Play 2 in Scala, using the Cake pattern might just be the perfect thing for you!

Dependency injection

Spring Framework, we have multiple modules; each module contains the code that is appropriate for the tier that the module implements. The code means the Java interfaces & their implementations as well as the Spring configuration (typically in the META-INF/spring/module-context.xml file). The types of the dependencies are checked at compile-time: if we haven't defined the UserService interface, for example, and tried to use it in the UserController, the compilation would fail. However, the compiler cannot check that the appropriate implementation of the UserService will be available to be injected to the UserController at runtime. The application is loosely coupled, which is good, but we must supply appropriate metadata (the annotations & the Spring XML) so that Spring can construct the application.

Dependency-injection applications must provide valid metadata so that the DI framework can construct their components & satisfy the dependencies between them at runtime.

Let's write a trivial application that contains a repository and a service. We will follow the recommended code to interfaces approach. This gives us the following code in the repository:

public interface UserRepository {
  List<User> findAll();
}

@Repository
public class ORMUserRepository implements UserRepository {
  private SessionFactory sessionFactory = // construct the SF
  public List<User> findAll() {
    // not real Hibernate code, but close enough
    return sessionFactory.getCurrentSession().query(...);
  }
}

We now combine this code with the Spring context file, which instructs the DI core to create the ORMUserRepository component.

<?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"
  xsi:schemaLocation="...">

  <context:annotation-config/>
  <context:component-scan base-package="...repository"/>

<beans/>

We implement the code in the services in using a similar pattern: we have the UserService interface and we implement it in some class. Crucially, we will rely on Spring to inject a valid implementation of the UserRepository to the instance of the DefaultUserService at runtime, using the configuration we provide.

public interface UserService {
  List<User> findAll();
}

@Service
public class DefaultUserService implements UserService {
  private final UserRepository userRepository;

  @Autowired
  public DefaultUserService(final UserRepository userRepository) {
    if (userRepository == null)
      throw new IllegalArgumentException("Off Santa's list!");
    this.userRepository = userRepository;
  }

  public List<User> findAll() {
    return this.userRepository.findAll();
  }
}

The services module needs its configuration, too. And so, we give the familiar /META-INF/spring/module-context.xml:

<?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"
  xsi:schemaLocation="...">

  <context:annotation-config/>
  <context:component-scan base-package="...services"/>

<beans/>

We have the components and the metadata. All we need now is to let Spring Framework create all the components. In other words, the ApplicationContext implementation that the Spring Framework provides will become our application:

public class App {

  public static void main(String[] args) {
    ApplicationContext application =
      new ClassPathXmlApplicationContext(
        "classpath*:/META-INF/spring/module-context.xml"));

    UserService service = application.getBean(UserService.class);

    // use it!
    service.findAll();
  }
}

The Spring Framework became our application! All we have is a shell that boots-up Spring, as shown on Figure 1.

Figure 1. Spring DI application

springapp

The Cake Pattern

If you are writing new Scala application, you might not want to take in all the baggage that Spring Framework brings with it. (Slf4j, anyone?)

You would typically use the Cake Pattern to achieve something akin to dependency injection. Jonas Boner explained the Cake Pattern in one of his infamous Real-World Scala Series blog post. What we would try here is to use the Cake pattern to implement the same DI scenario in Scala and then compare that with its corresponding Spring implementation. In doing that we would hope to clear out some of the misty clouds over Scala and DI.

We also want to have a repository and a service, but without the XML configuration file. So, we need not only the repository and service interfaces themselves, but we also need components in which they "live". Starting with the repository, we have:

trait UserRepositoryComponent {
  def userRepository : UserRepository

  trait UserRepository {
    def findAll: List[User]
  }
}

trait ORMUserRepositoryComponent extends UserRepositoryComponent {
  def userRepository = new ORMUserRepository(sf) //we need to actually instantiate the session factory here

  class ORMUserRepositry(val sf: SessionFactory) extends UserRepository {
    def findAll: List[User] = sf.query(...)
  }
}

Good. We have the repository tier interface the UserRepositoryComponent provides a way to get to the UserRepository instance. We have also shown an implementation of the repository interfaces: the ORMUserRepositoryComponent's userRepository function gives an ORM-based implementation of the UserRepository.

Now, we would like to define the interface to the services tier:

trait UserServiceComponent {
  def userService: UserService

  trait UserService {
    def findAll: List[User]
  }
}

OK, we have the repository interface & implementation and the service interface. The implementation of the service will need to have access to some implementation of the repository interface. Naturally, we don't want to specify which concrete repository interface implementation we want to use. In fact, we would like to let the users of our code specify arbitrary implementation of the repository interfaces. Good thing we have the composition inheritance!

trait DefaultUserServiceComponent extends UserServiceComponent {
  this: UserRepositoryComponent => 

  def userService = new DefaultUserService

  class DefaultUserService extends UserService {
    def findAll = userRepository.findAll
  }
}

Notice in particular this: UserRepositoryComponent =>. This means that the instance of DefaultUserServiceComponent depends on the UserRepositoryComponent. (It's a little more subtle than that, but for the purposes of this post it will do.) This is in function equivalent to Spring's @Autowired public DefaultUserService(final UserRepository userRepository). Just like the Spring application we have created, the Scala code does nothing yet. We need to wire it together in some kind of application!

object Application {
  val userServiceComponent = new DefaultUserServiceComponent with ORMUserRepositoryComponent
  val userService = userServiceComponent.userService
}

val userService = Application.userService
userService.findAll

In Scala, there is no ApplicationContext, so we have to implement our own application. But where's the dependency injection? Here: new DefaultUserServiceComponent with ORMUserRepositoryComponent, notice the with ORMUserRepositoryComponent. Figure 2 shows the Scala application.

Figure 2. Scala DI

cake application

The comparison

As you can see, the two approaches are completely different in their implementation. DI in Spring Framework is a runtime business and the component separation is left to the way in which we structure the configuration files. DI in Scala/Cake is a compile-time business with sharper component separation. Figure 3 gives the overall comparison.

Figure 3. The big picture

compare

Summary

As we have shown here, both pattern represent in function a kind of dependency injection & management. However, the crucial difference is that the dependency injection (in Spring Framework) is useful to manage components, leaving us to carefully design the components. The Cake Pattern allows us to inject & manage functionality. We use traits to assemble components with the functionality we require. It is a different approach to design. Let me repeat:

In plain dependency injection, we create components and we assemble these components together to form an application.
Using the Cake Pattern, we create pieces of functionality and we assemble the functionality to form the application.

 

Topics: PlayFramework, Play, spring

Subscribe to Email Updates