MacWire 0.1: Framework-less Dependency Injection with Scala Macros

31 Flares 31 Flares ×

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
4
5
6
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.:

1
2
3
4
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:

1
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.

Adam

  • http://twitter.com/przemekpokrywka Przemysław Pokrywka

    Adam, calling the approach interesting just wouldn’t do it justice!

    I already think MacWire is the most important advancement in
    Scala dependency injection techniques since the “Scalable Component
    Abstractions” paper (and especially its idea to use self-types for DI).

    With its current roadmap it’s on right way to become the best dependency injection method in Scala IMO.

    The thing I like the most here is you get fully type-safe auto-wiring. Other bonus is that constructors keep your objects honest about their dependencies – for me in most cases it’s more explicit than self-types in Cake pattern. Moreover it lets you avoid the whole boilerplate related to Cake. It’s worth stressing, that you don’t need the wrapper “modules” for your DI-ed classes. It simply rocks!

    I’m already planning to use it in my projects even with 0.1 version and I’ll keep my eye on the future development. Thank you for that great contribution for the Scala community!

  • rbelouin

    Nice stuff :)

  • http://www.warski.org/ Adam Warski

    Thanks! Looking forward to your feedback.

  • http://twitter.com/przemekpokrywka Przemysław Pokrywka

    As a proof of concept I’ve integrated it with Play! 2.1 sample application, which can be found on https://github.com/przemek-pokrywka/play-2.1-macwire-demo

    It works very well and met my expectations – the main one being to have a type-safe and concise way to do dependency injection. Now my wiring errors are detected even without running tests, in IntelliJ IDEA already.

    A small inconvenience while integrating with Play! was that there is currently no getInstance(Class)/getBean(Class) method equivalent. Wire method doesn’t cut it because of type erasure obviously. It would be handy to be able to get an object with similar method, so implementing Play’s getControllerInstance would become trivial.

    I’ve worked that around by introducing my own little cache, though the end-result is not that pretty, because user has to explicitly add wired Play! controllers to it. When she forgets that, the error will be thrown at runtime. It’d be good to have a fix for this inconvenience on the roadmap.

    It’d be also great to have this library cross-compiled for all minor versions of Scala, because now I get a warning: “org.scala-lang: 2.10.1, 2.10.0″.

    These are however small glitches and the library is perfectly usable even at this point.
    The crux of the integration is best shown on a diff:
    https://github.com/przemek-pokrywka/play-2.1-macwire-demo/commit/5016ed4a9b2003adc65087314f31db173530c5f5

    I’m counting on further development of MacWire, so we can really have the best library for DI in Scala. The start is very promising.

  • http://twitter.com/sourcedelica Eric Pederson

    For the singletons will you be supporting Scala objects? In the examples you give they are being instantiated with “new” so each module would get a separate instance of a given dependency – thus they aren’t actually singletons, no?

  • aloiscochard

    This is rougly the same approach I’m taking in sindi (github.com/aloiscochard/sindi) but with support of qualifier.

    I think qualifier could be ‘simulated’ with macwire usign TaggedType, but it sounds like polluting collaborators with DI concern to me.

  • http://www.warski.org/ Adam Warski

    Well, I read about sindi before, and now after taking another look, I think it takes the more traditional “container/framework” approach. First of all, you have a container :) Here it’s simply a trait + some code-generate new-s.

    How does exactly autowire[] work in Sindi? Is it type-safe, or does it use reflection?

    For me it seems that Sindi has a bit more boilerplate, but it may also be more powerful, and the development is obviously more advanced.

    I haven’t yet though at all about qualifier support, so I can’t comment much here if it’s possible to do in a nice way with the MacWire approach.

  • http://www.warski.org/ Adam Warski

    Well, they aren’t global singletons, true :) The nice thing here is that you can control how you want the object to behave. You can have an “module-singleton”, that is a dependency that is a singleton in a scope of a single module instantiation. This is the case when your module is a trait or a class. That way you can create multiple copies of whole graphs of objects, where each graph has its own environment and its own singletons.

    You can of course have a global-singleton, by using an object for the module, e.g.:


    object App {
    val globalSingleton = wire[Sth]
    }

  • aloiscochard

    Yes it’s typesafe using implicits, if using 2.9 it use kind of generated code to support it, but with 2.10 it use macro.

    I was thinking using the approach you use here for the next version where I don’t need to keep backward compatibility with 2.9.

    Some reflection is used in specific case, like when supporting ‘injectAll’ and plugins integration.

    I like some of your ideas, and I’ll probably reuse some of them to remove some needed of specific sindi type ;)

    Your approach is not compile-time free from any dependency as you need the macro definition ;-) so I don’t think it’s a big deal to introduce few types if that help support qualifier, but I agree being able to be free of dependency at runtime is quite nice, because it prevent any version nightmare!

    I should probably be able to introduce same concept in sindi when no qualifier are used, but as soon as you want some more advanced features (like plugins, qualifier, configuration integration…) I’ll need to make the library dependency mandatory.

    Good source of inspiration for making Sindi evolved!

    PS: I still wondering if you wouldn’t run in issue with current approach in regard to sharing singleton in multiple context, I’ll give you a concrete example if I find time to do so

  • http://www.warski.org/ Adam Warski

    Ah, you are probably using a compiler plugin?

    Glad that MacWire could be an inspiration to Sindi :) Feel free to extend the code of course.

    Agreed that to support e.g. web integration (request and session scopes) some library support will be needed. But it should be minimal, I think.

    Not sure what you mean with the singletons, so I’ll wait for an example. Maybe see also a comment below by Eric Pederson any my answer?

  • http://www.warski.org/ Adam Warski

    Nice! Thanks :)

    I suppose it should be possible to generate a getInstance()/getBean() map at compile-time with some additional macro. All the information is there, so it’s a matter of generating the code, I guess. I’ll add it to the “TODO” list.

    As for compiler warnings, since 2.10 there’s no minor-version-cross-compilation, the resulting artifacts only have _2.10 appended. So you can only generate a 2.10-compatible version, not a 2.10.0-compatible one. The warning is probably going to be fixed in the next SBT.

  • aloiscochard

    and how do you change the implementation for running integration tests?

  • aloiscochard

    No I’m not using a compiler plugin.

    The inital version of sindi was (0.5) but the new one rewrote from scratch (1.0) doesn’t, it’s all done using type level programming (exept when autowiring is used with 2.10 where a macro is used)

    Agree about library support as to be minimal, it’s clearly what I’m trying to do with sindi, it’s very compact and lightweight.

    About singleton see my response below.

  • http://www.warski.org/ Adam Warski

    There’s an example in the blog, just override a value in the trait, e.g.


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

  • aloiscochard

    No, I’m asking how would you change the implementation of a global singleton during tests?

    You can’t.

  • http://www.warski.org/ Adam Warski

    True, that’s why you shouldn’t use global singletons.

  • aloiscochard

    Sure, agreed, but unfortunately sometimes some web-framework make it hard to avoid it.

  • http://www.warski.org/ Adam Warski

    But then maybe you could have a global reference to an object which is an instance of trait Beans, where Beans contain all the dependencies? Depending if you are doing integration testing or not, you could substitute the object for a TestBeans instance?

    Anyway, this is quite use-case specific (“how to hack web framework X”), so hard to discuss in a general way.

  • http://twitter.com/przemekpokrywka Przemysław Pokrywka

    @Adam, @Alois

    It’s not only about singletons and not only web frameworks. Every
    time a framework grabs full control over object creation troubles are on the way. Fortunately this problem is well known and good solutions already exist, only most developers are ignorant about it.

    In general it’s best to just take it back from the framework. Surprisingly you can do it in Liftweb and Play! That’s the framework-specific part.

    When that’s not the option, it’s best to reduce that object’s logic to
    lookup and creation, like advised at http://misko.hevery.com/2009/04/08/how-to-do-everything-wrong-with-servlets/ (bit long, but worth reading).
    That way all the interesting logic is still testable and end-to-end tests cover the wiring that remained.

    A “global reference to an object which is an instance of trait Beans” (AKA Service Locator pattern) is IMO a worse solution – I cannot imagine a realistic example where it’d work better, than just extracting the logic to a separate, testable object.

  • http://twitter.com/przemekpokrywka Przemysław Pokrywka

    I don’t mind being dependent on some DI-framework code (be it MacWire, Sindi or any other) as long as that dependency doesn’t escape my set-up code (called sometimes the “end-of-the-world”, where all the wiring happens) and when I can easily rewrite it to plain old constructor calls.

  • http://twitter.com/sourcedelica Eric Pederson

    Often times the singleton is configured a specific way and/or has state – you need to make sure all of the modules refer to that specific (singleton) object.

  • peter

    Why, oh, would you that instead of plain traits and trait composition. *doh*

  • http://www.warski.org/ Adam Warski

    You mean the cake pattern? Or how? Trait composition has its uses and is very useful, but not necessarily for this scenario?

  • peter

    Whenever you need dependency you just specify it as a anonymous member in a trait:

    trait MyLogic { … val theDatabaseAccess: DatabaseAccess … }

    In order to fulfilll it you fill the fields mixin all the parts together in an object:

    object MyLogicProduction extends MyLogic { val theDatabaseAccess = new DatabaseAccess() }

    What’s the benefit of macwire over this simple use of default language features?

  • http://www.warski.org/ Adam Warski

    Yeah, so that’s exactly the cake pattern if I’m not mistaken. It is another way to do DI, with its good and bad sides … for some summary see my earlier blog post: http://www.warski.org/blog/2011/04/di-in-scala-cake-pattern-pros-cons/, I don’t think there’s much sense in re-writing it here :)

    Btw. MacWire also uses “basic language features”, that is constructors. wire[] is just a convenience macro to reduce the need to write parameters explicitly in the new X(…) invocation.

31 Flares Twitter 21 Facebook 2 Google+ 5 LinkedIn 3 Email -- 31 Flares ×