Fun with Stamps. Episode 7. Early and late dependency injection

Vasyl Boroviak
4 min readJun 9, 2016

Hello. I’m developer Vasyl Boroviak and welcome to the seventh episode of Vasyl Boroviak presents Fun with Stamps.

The most used feature of stamps is the dependency injection.

When every programmer on the planet thinks of dependency injection (DI) he usually think of the on-the-spot DI. In other words — injecting dependencies at the time of creating objects. Let’s call it late dependency injection.

Late Dependency Injection

Assuming we have 4 stamps imported from somewhere else:

import {AccessLogClient, Logger, AuthServiceClient, UserAccess} from './a-file-with-stamps';

And a http request handler which should grant access to a resource for a user:

const accessLogClient = AccessLogClient(config.access_log.uri);export default function requestHandler(request, reply) {  const logger = Logger({uri: request.uri});
const authServiceClient = AuthServiceClient(request.payload.key);
const userAccess = UserAccess({
logger, // late dependency injection
authServiceClient,
// late dependency injection
accessLogClient
// late dependency injection
});
const {user, resource} = request.payload;
reply(userAccess.grantAccess(user, resource));
}

In the above example we pass all three dependencies each time the UserAccess instance is created.

The code above is easy to unit test.

describe('UserAccess', () => {  const mockedLogger = console;                     // dependency 1
const mockedAccessLogClient = { logAccess() {} }; // dependency 3
it('grantAccess should succeed', () => { function canAccess() {
if (id === '42') return Promise.resolve();
return Promise.reject();
}
const mockedAuthServiceClient = {canAccess}; // dependency 2
return UserAccess({
logger: mockedLogger, // dependency 1
authServiceClient: mockedAuthServiceClient, // dependency 2
accessLogClient: mockedAccessLogClient // dependency 3
})
.grantAccess({id: '42'}, {directory: '/private'});
});});

It can become too repetitive when you need to pass both logger and accessLogClient in every unit test out there. DRY FTW!

Don’t Repeat Yourself For The Win!

With stamps you can inject those dependencies in the before() mocha/karma hook and then just pass the only dependency left — authServiceClient. We will call it early dependency injection.

Early Dependency Injection

Early dependency injection is when you tweak your class/stamp/factory to have a different default dependency. We will pre-setup our stamp before creating an object instance form it.

Let’s rewrite the unit test above with an early DI.

describe('UserAccess', () => {  const mockedLogger = console;                     // dependency 1
const mockedAccessLogClient = { logAccess() {} }; // dependency 3
before(() => {
UserAccess = UserAccess.compose({
// pre-setup
properties: {

logger: mockedLogger,
// dependency 1
accessLogClient: mockedAccessLogClient
// dependency 3
}
});
});
it('grantAccess should succeed', () => { function canAccess() {
if (id === '42') return Promise.resolve();
return Promise.reject();
}
const mockedAuthServiceClient = {canAccess}; // dependency 2
return UserAccess({
authServiceClient: mockedAuthServiceClient, // dependency 2
})
.grantAccess({id: '42'}, {directory: '/private'});
});});

Amazing! We don’t need to pass all three dependencies any more! Just the single one we need.

Also, take a look at the requestHandler function.

const accessLogClient = AccessLogClient(config.access_log.uri);export default function requestHandler(request, reply) {  const logger = Logger({uri: request.uri});
const authServiceClient = AuthServiceClient(request.payload.key);
const userAccess = UserAccess({
logger,
authServiceClient,
accessLogClient
// injected every time
});
const {user, resource} = request.payload;
reply(userAccess.grantAccess(user, resource));
}

As you can see we can early inject the accessLogClient dependency too.

const accessLogClient = AccessLogClient(config.access_log.uri);// early Dependency Injection
UserAccess = UserAccess.compose({
properties: {
accessLogClient
// early Dependency Injection
}
});
export default function requestHandler(request, reply) { const logger = Logger({uri: request.uri});
const authServiceClient = AuthServiceClient(request.payload.key);
const userAccess = UserAccess({
logger,
authServiceClient
}); // no need to pass it any more
const {user, resource} = request.payload;
reply(userAccess.grantAccess(user, resource));
}

Now the accessLogClient is injected only once on the application startup.

Have fun with stamps!

--

--