Last active
January 2, 2019 22:27
-
-
Save MikeyBurkman/f406ed52e363f1fe475eb4af1f597a07 to your computer and use it in GitHub Desktop.
TS Dependency Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Dependency Example in TS. | |
* Instead of dependency injection, this method shows how services can "fetch" their dependencies, | |
* without being concerned about the lifecycles of their dependencies. | |
* These services are also isolated from their dependencies, lending themselves to easier testing. | |
* Does not use classes or decorators or any black magic to accomplish this. | |
* | |
* All services are essentially split into 3 pieces: | |
* 1. The interface that defines the service to other services. | |
* 2. The service constructing function that takes in all depedencies as arguments. | |
* 3. The instance getter function, that other services will import in their own instance getter functions | |
* See comments on each function below for more info. | |
*/ | |
// Baz service | |
import { memoize, repeat } from 'lodash'; | |
import { Counter, counter } from './counter'; | |
// Anything that needs a Baz service will ask for this interface in their constructing function. | |
// This is essentially the service's contract. | |
export interface Baz { | |
/** | |
* Documentation for the shout function goes here. | |
* This is a silly little function with no real meaning right now. It returns a string... | |
*/ | |
shout: () => string; | |
} | |
// This next function contains all the logic for the Baz service, but has zero dependencies outside | |
// of its arguments. | |
// (Exceptions can of course be made, like for a logger.) | |
// Basically, anything that would need to be mocked in unit tests should be an argument. | |
// This function would be imported by the unit tests, and could be easily constructed with | |
// mock values for testing. | |
// Nothing outside of this file and unit tests will ever call this function, so adding dependencies | |
// at a later date will not break callers unless it breaks the interface. | |
export const newBaz = (word: string, counter: Counter): Baz => { | |
const shout = () => { | |
counter = counter.increment(); | |
const count = counter.value(); | |
return repeat(word, count); | |
}; | |
return { shout }; | |
}; | |
// This is a no-arg function that returns the singleton instance. | |
// This function is imported by other services, but does NOT contain any logic, and will | |
// NOT be unit tested. (Integration tests will test it. The compiler itself also helps a lot here.) | |
// This function is all about assembling dependencies, whether they be config | |
// values, or implementations of other dependencies. | |
// The memoize() is a bit weird, but it also ensures that it's essentially a lazily-initiallized | |
// instance. If this instantiation were instead done in its own file, then node's module resolver would | |
// essentially handle this lazy initialization for us. (It would get created only when that file is imported.) | |
// (We want it to be lazily-initialized so that unit tests importing this file won't | |
// inadventently call the instantiation logic, which might lead to undesired side effects. It might not be necessary) | |
// If your service is stateful for some reason, and you don't want a singleton, just simply leave out the memoize() bit | |
// and you'll get a new instance every time. | |
export const baz = memoize( | |
(): Baz => { | |
const word = 'bananas '; // This could could come from the config, for instance | |
const counterInstance = counter(); // Note -- no args ever passed to this function either | |
return newBaz(word, counterInstance); | |
} | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Counter service | |
// Follows the same pattern as baz.ts, except this doesn't have any external dependencies | |
import { memoize } from 'lodash'; | |
export interface Counter { | |
increment: () => Counter; | |
value: () => number; | |
} | |
export const newCounter = (value: number): Counter => { | |
return { | |
increment: () => newCounter(value + 1), | |
value: () => value | |
}; | |
}; | |
export const counter = memoize(() => newCounter(0)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Just a silly file to show how we can import the baz getter and use it | |
// without knowing anything about Baz's dependencies. | |
import { baz } from './baz'; | |
console.log(baz().shout()); // "bananas " | |
console.log(baz().shout()); // "bananas bananas " | |
console.log(baz().shout()); // "bananas bananas bananas " |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment