IoCV3 Upgrade Notes

This document explains some of the differences between IoC 2 and IoC 3.

IoC 3 is a complete rewrite of the IoC library, but the main interface to the library (service facets) has remained the same. This allowed the extensive test coverage of IoC 2 to be reused for IoC 3, and ensures your code requires little, if any, change to update.

A major feature of IoC 3 is that it is available in Javascript. To allow this, the Proxy and Aspect features of IoC 2 have been dropped. A side effect being that IoC is now faster and more lightweight than ever before. Proxies, if required, can generally be replaced by Lazy Funcs, and it was felt Aspects were an advanced, and generally under utilised feature.

On to the changes:

RegistryBuilder

RegistryBuilder now accepts module instances as well as module types. Note the startup() method has been dropped.

Old code:

registry := RegistryBuilder().addModule(MyModule#).build().startup()

New code:

registry := RegistryBuilder().addModule(MyModule()).build()

RegistryBuilder is a lot more versatile and is able to do everything an AppModule can, and more! An example using with() it-block syntax:

registry := RegistryBuilder() {
    addModule(MyModule())

    // add services and contribute to them
    addService(Penguins#).withCtorArgs(["fishLegs"])
    contributeToServiceType(Penguins#) |Configuration config| {
        config["food"] = Fish()
    }

    // add hooks to lifecycle events
    onRegistryStartup          |Configuration config| { ... }
    onRegistryShutdown         |Configuration config| { ... }
    onScopeDestroy("root")     |Configuration config| { ... }
    onScopeDestroy("root")     |Configuration config| { ... }
    onServiceBuild("penguins") |Configuration config| { ... }

}.build()

AppModules

AppModules must now be const because instances may be contributed to RegistryBuilder. This means methods no longer need to be static, which feels more natural.

defineServices() may be called anything as long as the method name starts with defineXXXX() and declares RegistryBuilder as its only parameter.

Methods annotated with @Build, @Contribute and @Override remain the same.

The RegistryStartup and RegistryShutdown services have been replaced with lifecycle hooks, which may be contributed to via special named methods.

Other syntax differences are shown below:

Old code:

class AppModule {

    static Void defineServices(ServiceDefinitions defs) {
        defs.add(MyService#).withCtorArgs(["fishLegs"])
        ...
    }

    @Build
    static Penguins buildPenguins() { ... }

    @Contribute { serviceType=Penguins# }
    static Void contributePenguinUrls(Configuration config) { ... }

    @Contribute { serviceType=RegistryStartup# }
    static Void contributeRegistryStartup(Configuration config) { ... }

    @Contribute { serviceType=RegistryShutdown# }
    static Void contributeRegistryShutdown(Configuration config) { ... }
}

New code:

const class AppModule {

    Void defineModule(RegistryBuilder bob) {
        bob.addService(MyService()).withCtorArgs(["fishLegs"])
        ...
    }

    @Build
    Penguins buildPenguins() { ... }

    @Contribute { serviceType=Penguins# }
    Void contributePenguinUrls(Configuration config) { ... }

    Void onRegistryStartup(Configuration config) { ... }

    Void onRegistryShutdown(Configuration config) { ... }
}

No changes have been made to the Configuration class other than contributions may now be optional.

Registry / Scope

The biggest change in IoC 3 is the introduction of Scopes. As such, most of the useful dependency injection methods have moved from Registry to the new Scope class. The method names have been tidied up too.

Old code:

registry.serviceById("myService")
registry.dependencyByType(MyService#)
registry.autobuild(MyService#)
registry.injectIntoFields(MyService())

New code:

scope := registry.activeScope()
scope.serviceById("myService")
scope.serviceByType(MyService#)
scope.build(MyService#)
scope.inject(MyService())

Note that only services may now be retrieved from Scope, should you wish for a general dependency, use the DependencyProviders service - see source for details.

Scopes

As previously mentioned, the biggest change in IoC 3 is the introduction of Scopes. IoC itself defines an application level root scope where all singletons may live. For non-const classes BedSheet defines a threaded request scope and Reflux defines a uiThread scope.

Const services are automatically matched to the root scope (and other non-threaded scopes), and non-const services are matched to threaded scopes. In containers such as BedSheet and Reflux where there are just 2 scopes, this default behaviour matches IoC 2.

However, if creating custom scopes then you should take care that services are only created in prescribed scopes.

registry := RegistryBuilder() {
    addScope("southPole")
    addService(Penguins#).withScope("southPole")
}

registry.rootScope.createChild("southPole") |Scope southPole| {
    southPole.serviceByType(Penguins#)
    ...
}

Have fun!