Dojorama Part 5: Dependency Injection Container And Configuration

This article is part of the series Dojorama: Building a Dojo Single Page Application

Remember how we requested our store from dojorama/service/release-store in part 4 of this article series? There is only one problem with this approach - the store is hardcoded into the service module. We could of course, add a configuration switch and return a different store based on the current context:

define([
    "dojo/_base/config",
    "dojo/store/JsonRest",
    "SomeOtherStore"
], function (
    config,
    JsonRest,
    SomeOtherStore
) {
    if (config['service/release-store'].context === 'dev') {
        return new SomeOtherStore();
    }

    return new JsonRest();
});

But again, this isn't ideal either. Our library gets polluted with code fragments that are never used in production. What's needed is some sort of configurable dependency injection container to manage service object instantiations.

Turns out that Dojo's AMD loader along with the config object can be used perfectly as a dependency injection container. However, you wanna try out this stuff a little bit off to the side, where you're not gonna hurt anybody. When baking production builds, the build tool will throw some nasty errors at you but it actually doesn't matter. The resulting build is perfectly functional! It goes like this:

dojorama/service/release-store is still our service module but this time we'll feed dynamic arguments to define(). For our purposes, we'll pass an array with the service module's dependencies and a callback function in charge of initializing the object. What if we could provide those arguments from configuration? In fact, we can:

service/release-store.js

define(
    require.rawConfig['service/release-store'].deps,
    require.rawConfig['service/release-store'].callback
);

... and here's dojoConfig:

var dojoConfig = {
    'service/release-store': {
        deps: ['dojo/store/JsonRest'],
        callback: function (JsonRest) {
            return new JsonRest({
                target: "/api/releases/",
                idProperty: "id"
            });
        }
    },
};

... declaring a dependency with the service module:

define(["../service/release-store"], function (releaseStore) {
    var result = releaseStore.get('123');
});

This technique turns dojoConfig into a sort of composition root for our service modules. We can swap out the returned store just by changing the configuration. No extra code, everything already built in thanks to Dojo's Loader. The only price we pay, is one HTTP request per service module plus the pain of the build errors...

A Word Of Warning

As mentioned before, Dojo's build tool will complain when confronted with this setup. You'll get the following error for each service module:

error(307) Failed to evaluate module tagged as pure AMD (fell back to processing with regular expressions). module: dojorama/service/release-store; error: TypeError: Cannot read property 'dojorama/service/release-store' of undefined

This is because the build tool cannot and shouldn't resolve the dependencies. It shouldn't because if it would, our configuration would be baked into the build...

Object Store Substitution During Development And Testing

Now that we have a way of setting service modules from configuration, let's go back to our previous case: Until the API is ready, we would like to use dojo-local-storage/LocalStorage which is available from Github.

Due to the nature of LocalStorage, we need a way to flag release objects, so we can query for them specifically and not have the full list of LocalStorage content listed in our releases application. And most importantly, this should be handled transparently to our application - we definitely don't want to clutter our code with extra object properties and query parameters that are specific to the store which is currently in use.

Transparently to a client, Dojo-local-storage adds a property to each object it stores and removes it upon retrieval. Name and value of this property can be passed to the constructor upon instantiation, which is what we are doing in the snippet below:

var dojoConfig = {
    'service/release-store': {
        deps: ['dojo-local-storage/LocalStorage'],
        callback: function (LocalStorage) {
            return new LocalStorage({
                subsetProperty: 'dojoramaSubset',
                subsetName: "releases",
                idProperty: "id"
            });
        }
    },
};

Note how we use the module Id (without package name) as the configuration key. This gets us a clean and understandable configuration object and simplifies refactorings.

Conclusion

Hopefully, those with the capacity of really understanding the Dojo loader and build tool, will not reveal this as an anti-pattern or other intolerable hack. Either way, this shows the incredible power and flexibility of the loader - it's one of the best things that could have possibly come to the world of JavaScript application development.

Let's not stick around here for too long and move on with visual inspection and unit testing.