Performance Zone is brought to you in partnership with:

I am the founder and lead developer of Hibernate Envers, a Hibernate core module, which provides entity versioning/auditing capabilities. I am also one of the co-founders of SoftwareMill, a company specializing in delivering customized software solutions (http://softwaremill.com, "Extraordinary software as a standard"), based on Java and JBoss technologies. After work, apart from being involved in development of Envers, I work on several small open source projects, like ElasticMQ (simple message queue written in Scala with an SQS interface), projects around static analysis (using JSR 308 - Typestate Annotations/ Checkers Framework and FindBugs), and some CDI/Weld (not always portable) extensions, like autofactories or stackable security interceptors. I am also interested in new JVM-based languages, especially with functional elements (like Scala, JRuby) and frameworks built using them (like Lift), as well as improving the ways we use Dependency Injection. Adam is a DZone MVB and is not an employee of DZone and has posted 51 posts at DZone. You can read more from them at their website. View Full User Profile

MacWire 0.1: Framework-less Dependency Injection with Scala Macros

04.08.2013
| 3398 views |
  • submit to reddit

Using Dependency Injection is almost a standard when developing software. However, in many cases it may seem that using the pattern implicates using a DI container/framework. But is a framework really needed?

To implement DI all you really need is to remove the news from your code, and move the dependencies to the constructor. There must be of course some place where the objects are created; for that you need a top-level (“main” class), where all the wiring is done. That’s where DI containers really help: they remove the tedious task of passing the right parameters to the constructors. Usually that’s done at run-time using reflection.

MacWire takes a different approach. Basing on declarations specifying which classes should be instantiated, it generates the code needed to create a new class instance, with the correct parameters taken from the enclosing type. This is done at compile-time using a Scala Macro. The code is then type-checked by the Scala compiler, so the whole process is type-safe, and if a dependency is missing, you’ll know that immediately (unlike with traditional DI containers).

For example, given:

class DatabaseAccess()
class SecurityFilter()
class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter)
class UserStatusReader(userFinder: UserFinder)
 
trait UserModule {
    import com.softwaremill.macwire.MacwireMacros._
 
    lazy val theDatabaseAccess   = wire[DatabaseAccess]
    lazy val theSecurityFilter   = wire[SecurityFilter]
    lazy val theUserFinder       = wire[UserFinder]
    lazy val theUserStatusReader = wire[UserStatusReader]
}

The generated code will be:

trait UserModule {
    lazy val theDatabaseAccess   = new DatabaseAccess()
    lazy val theSecurityFilter   = new SecurityFilter()
    lazy val theUserFinder       = new UserFinder(theDatabaseAccess, theSecurityFilter)
    lazy val theUserStatusReader = new UserStatusReader(theUserFinder)
}

The classes that should be wired should be contained in a Scala trait, class or object (the container forms a “module”). MacWire looks up values from the enclosing type (trait/class/object), and from any super-traits/classes. Hence it is possible to combine several modules using inheritance.

Currently two scopes are supported; the dependency can be a singleton (declared as a val/lazy val) or a new instance can be created for each usage (declared as a def).

Note that this approach is very flexible; all that we are dealing with here is regular Scala code, so if a class needs to be created in some special way, there’s nothing stopping us from simply writing it down as code. Also, wire[T] can be nested inside a method’s body, and it will be expanded to new instance creation as well.

For integration testing a module, if for some classes we’d like to use a mock, a simple override suffices, e.g.:

trait UserModuleForTests extends UserModule {
    override lazy val theDatabaseAccess = mockDatabaseAccess
    override lazy val theSecurityFilter = mockSecurityFilter
}

The project is a follow up of my earlier blog post. There is also a similar project for Java, which uses annotation processors: Dagger.

MacWire is available in Maven Central. To use, simply add this line to your SBT build:

libraryDependencies += "com.softwaremill.macwire" %% "core" % "0.1"

The code is on GitHub, licensed under the Apache2 license, so feel free to use, fork & explore. Take a look at the README which contains some more information.

Future plans include support for factories and by-name parameter lookup, support for configuration values and more scopes, like request or session scopes, which are very useful for web projects.



Published at DZone with permission of Adam Warski, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)