Tuesday, August 19, 2014

A simple dependency injection convention for Go

Steve Powell and I are experimenting with a minimal DI convention for Go which simplifies how we construct values of Go interfaces and use mocks of dependencies during testing.


We are building a layered component and need to test the packages in isolation from each other. So we've introduced interfaces. Each interface usually has one exported creation function named something like NewXXX() which constructs a value of the interface. Packages in higher layers depend on interface values from packages in lower layers and so we pass these values into the creation function.

These NewXXX()functions are tied to the implementation of a package, so we wanted some way to organise the way in which these functions are used. The convention makes these functions private, with names like newXXX(), and then provides separate wiring functions which are used by higher layers and test code.

Although we work for SpringSource, we were hesitant to introduce code generation or reflective "magic" to do the wiring as those seemed contrary to Go's design philosophy of keeping things simple (even if a bit more code needs writing). So we made sure the wiring functions are patterned and simple to read and write.

The lowest layers have trivial wiring functions, for example:

func Wire() (Fileutils, error) {
    return WireWith()
}

func WireWith() (Fileutils, error) {
    return newFileutils()
}

whereas higher layers are somewhat more involved although extremely patterned and the wiring functions simply call lower level wiring functions, for example:

func Wire(depotPath string, rwBaseDir string) (warden.Backend, 
                                               error) {
    rootfs, err := rootfs.Wire(rwBaseDir)
    if err != nil {
        return nil, err
    }

    configBuilder, err := config_builder.Wire()
    if err != nil {
        return nil, err
    }

    return WireWith(depotPath, rootfs, configBuilder)
}

func WireWith(depotPath string, rootfs rootfs.RootFS, configBuilder 
              config_builder.ConfigBuilder) (warden.Backend, 
                                             error) {
return newGuardianBackend(depotPath, rootfs, configBuilder)
}

Note: the examples above use standard Go errors, but our code actually uses a special error type described in a previous blog.

For a detailed description of the convention, see this commit log.

Projects

OSGi (130) Virgo (59) Eclipse (10) Equinox (9) dm Server (8) Felix (4) WebSphere (3) Aries (2) GlassFish (2) JBoss (1) Newton (1) WebLogic (1)