Fun with Stamps. Episode 7. Early and late dependency injection
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!
The rest of the episodes:
- Episode 1. Stamp basics
- Episode 2. Dependency injection in FP
- Episode 3. Comparing with the ES2015 classes
- Episode 4. Implementing stamps yourself in 30 LOC
- Episode 5. Composition design pattern
- Episode 6. Statics — properties on stamps
- Episode 7. Early and late dependency injection (this article)
- Episode 8. Tracking and overriding composition
- Episode 9. Detaching compose()
- Episode 10. My stamp mental model
- Episode 11. Interfering composition
- Episode 12. New @stamp home
- Episode 13. Method collision control
- Episode 14. New @stamp/it as a replacement of Stampit
- Episode 15. The @stamp/ modules ecosystem
- Episode 16. TypeScript mix-in classes vs Stamps
- Episode 17. Easy 100% unit test coverage in JS
- Episode 18. Dependency injection paradise
- Episode 19. Java/C# abstract methods in JavaScript
- Episode 20. Stampit v4
- Episode 21. Private data in JavaScript. 4 ways using stamps
- Episode 22. JavaScript instanceof as composable stamp
- Episode 23. New stampit.js.org with all the docs
- Episode 24. New “name” feature