-
DI in Scala: Cake Pattern pros & cons
Posted on April 29th, 2011 28 commentsI’ve been looking at alternatives for java-style DI and DI containers which would use pure Scala; a promising candidate is the Cake Pattern (see my earlier blog post for information on how the Cake Pattern works). FP enthusiast also claim that they don’t need any DI frameworks, as higher-order functions are enough.
Recently Debasish Ghosh also blogged on a similar subject. I think his article is a very good introduction into the subject.
Below are some problems I encountered with the Cake Pattern. (Higher-order functions are coming up in the next post.) If you have solutions to any of them, let me know!
Parametrizing the system with a component implementation
First of all, it is not possible to parametrize a system with a component implementation. Supposing I have three components:
DatabaseComponent,UserRepositoryComponent,UserAuthenticatorComponentwith implementations, the top-level environment/entry point of the system would be created as follows:val env = new MysqlDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent
Now to create a testing environment with a mock database, I would have to do:
val env = new MockDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent
Note how much of the code is the same. This isn’t a problem with 3 components, but if there are 20? All of them but one have to be repeated just to change the implementation of one component. This clearly leads to quite a lot of code duplication.
Component configuration
Quite often a component needs to be configured. Let’s say I have a
UserAuthenticatorComponentwhich depends onUserRepositoryComponent. However, the authenticator component has an abstract valencryptionMethod, used to configure the encryption algorithm. How can I configure the component? There are two ways. The abstract val can be concretized when defining the env, e.g.:val env = new MysqlDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent { val encryptionMethod = EncryptionMethods.MD5 }But what if I want to re-use a configured component? An obvious answer is to extend the
UserAuthenticatorComponenttrait. However if that component has any dependencies (which, in the Cake Pattern, are expressed using self-types), they need to be repeated, as self-types are not inherited. So a reusable, configured component could look like this:trait UserAuthenticatorComponentWithMD5 extends UserAuthenticatorComponent { // dependency specification duplication! this: UserRepositoryComponent => val encryptionMethod = EncryptionMethods.MD5 }If we don’t repeat the self-types, the compiler will complain about incorrect
UserAuthenticatorComponentusage.No control over initialization order
A problem also related to configuration, is that there is no type-safe way to assure that the components are initialized in the proper order. Suppose as above that the
UserAuthenticatorComponenthas an abstractencryptionMethodwhich must be specified when creating the component. If we have another component that depends onUserAuthenticatorComponent:trait PasswordEncoderComponent { this: UserAuthenticatorComponent => // encryptionMethod comes from UserAuthenticatorComponent val encryptionAlgorithm = Encryption.getAlgorithm(encryptionMethod) }and initialize our system as follow:
val env = new MysqlDatabaseComponentImpl with UserRepositoryComponent with UserAuthenticatorComponent with PasswordEncoderComponent { val encryptionMethod = EncryptionMethods.MD5 }then at the moment of initialization of
encryptionAlgorithm,encryptionMethodwill benull! The only way to prevent this is to mix in theUserAuthenticatorComponentWithMD5before thePasswordEncoderComponent. But the type checker won’t tell us that.Pros
Don’t get me wrong that I don’t like the Cake Pattern – I think it offers a very nice way to structure your programs. For example it eliminates the need for factories (which I’m not a very big fan of), or nicely separates dependencies on components and dependencies on data (*). But still, it could be better ;).
(*) Here each code fragment has in fact two types of arguments: normal method arguments, which can be used to pass data, and component arguments, expressed as the self type of the containing component. Whether these two types of arguments should be treated differently is a good question :).
What are your experiences with DI in Scala? Do you use a Java DI framework, one of the approaches used above or some other way?
Adam
-
Dependency Injection in Scala: Extending the Cake Pattern
Posted on December 14th, 2010 20 commentsContinuing the mini-series on Dependency Injection (see my previous blogs: problems with DI, assisted inject for CDI and improving assisted inject), I took a look at how DI is handled in Scala.
There are several approaches, one of the most interesting being the Cake Pattern. It is a DI solution that uses only native language features, without any framework support. For a good introduction see either Jonas Boner’s blog (on which this post is largerly based) or Martin Odersky’s paper Scalable Component Abstractions.
I would like to extend the Cake Pattern to allow defining dependencies which need some user-provided data to be constructed (like in autofactories/assisted inject).
The Cake Pattern: interfaces
But let’s start with an example of the base pattern. Let’s say that we have a
Userclass,sealed case class User(username: String)
and that we want to create a
UserRepositoryservice. Using the Cake Pattern, first we create the “interface”:trait UserRepositoryComponent { // For expressing dependencies def userRepository: UserRepository // Way to obtain the dependency trait UserRepository { // Interface exposed to the user def find(username: String): User } }We have three important things here:
- the
UserRepositoryComponenttrait will be used to express dependencies. It contains the component definition, consiting of: - a way to obtain the dependency: the
def userRepositorymethod (could also be aval, but why adefis better I’ll explain later) - the interface itself, here a
UserRepositorytrait, which gives the functionality of locating users by username
The Cake Pattern: implementations
An implementation of a component looks pretty similar:
trait UserRepositoryComponentHibernateImpl extends UserRepositoryComponent { def userRepository = new UserRepositoryImpl class UserRepositoryImpl extends UserRepository { def find(username: String): User = { println("Find with Hibernate: " + username) new User(username) } } }Nothing special here. The component implementation extends the “interface” component trait. This brings into scope the
UserRepositorytrait, which can be implemented.Using dependencies
How can one component/service say that it depends on another? Scala’s self-type annotations are of much use here. For example, if a
UserAuthorizationcomponent requires theUserRepository, we can write this as follows:// Component definition, as before trait UserAuthorizationComponent { def userAuthorization: UserAuthorization trait UserAuthorization { def authorize(user: User) } } // Component implementation trait UserAuthorizationComponentImpl extends UserAuthorizationComponent { // Dependencies this: UserRepositoryComponent => def userAuthorization = new UserAuthorizationImpl class UserAuthorizationImpl extends UserAuthorization { def authorize(user: User) { println("Authorizing " + user.username) // Obtaining the dependency and calling a method on it userRepository.find(user.username) } } }The important part here is
this: UserRepositoryComponent =>. By this code fragment we specify that theUserAuthorizationComponentImplrequires some implementation of theUserRepositoryComponent. This also brings the content of theUserRepositoryComponentinto scope, so both the method to obtain the user repository and theUserRepositorytrait itself are visible.Wiring
How do we wire different components together? Again quite easily. For example:
val env = new UserAuthorizationComponentImpl with UserRepositoryComponentHibernateImpl env.userAuthorization.authorize(User("1"))First we need to construct the environment, by combining all of the components implementations that we want to use into a single object. Next, we can call methods on the environment to obtain services.
What about testing? Also easy:
val envTesting = new UserAuthorizationComponentImpl with UserRepositoryComponent { def userRepository = mock(classOf[UserRepository]) } envTesting.userAuthorization.authorize(User("3"))Here we have mocked the user repository, so we can test the
UserAuthorizationComponentImplin isolation.defs overvalsWhy are
defs in the component definition better as the way to obtain the dependency? Because if you use aval, all implementations are locked and have to provide a single dependency instance (a constant). With a method, you can return different values on each invocation. For example, in a web environment, this is a great way to implement scoping! The method can read from the request or session state. Of course, it is still possible to provide a singleton. Or a new instance of the dependency on each invocation.Dependencies that need user data
Finally, we arrive to the main point. What if our dependencies need some data at runtime? For example, if we wanted to create a
UserInformationservice, which wraps aUserinstance?Well, who said the the methods by which we obtain the dependencies need to be parameterless?
// Interface trait UserInformationComponent { // What is needed to create the component def userInformation(user: User) trait UserInformation { def userCountry: Country } } // Implementation trait UserInformationComponentImpl extends UserInformationComponent { // Dependencies this: CountryRepositoryComponent => def userInformation(user: User) = new UserInformationImpl(user) class UserInformationImpl(val user: User) extends UserInformation { def userCountry: Country { // Using the dependency countryRepository.findByEmail(user.email) } } } // Usage val env = new UserInformationComponentImpl with CountryRepositoryComponentImpl env.userInformation(User("someuser@domain.pl")).userCountryIsn’t this better than passing the
Userinstance as a method parameter?Using the Cake Pattern, creating stateful dependencies, which can be created at run-time with user-provided data, and still depend on other components is a breeze. This is similar to a factory method, however with much less noise.
The good and the bad
The good:
- no framework required, using only language features
- type safe – a missing dependency is found at compile-time
- powerful – “assisted inject”, scoping possible by implementing the dependency-providing method appropriately
The bad:
- quite a lot of boilerplate code: each component has a component interface, implementation, service interface and service implementation
However, I don’t think defining all four parts is always necessary. If there’s only one implementation of a component, you can combine the component interface and implementation into one, and if there’s a need, refactor later.
Adam
- the






Twitter