Vinyl uses the most common form of dependency injection; requesting dependencies through constructors.
Class constructors have a deps parameter describing the required dependencies.
E.g.
/**
* Dependencies for {@link RequesterImpl}
*/
export type RequesterImplDeps = {
readonly networkState: NetworkState
readonly fetch: Fetch
readonly networkMetricsController: NetworkMetricsController
}
export class RequesterImpl {
constructor(protected readonly deps: RequesterImplDeps) {}
// ...
}
const requester = new Requester({
networkState: new NetworkStateImpl(),
fetch: new FetchImpl(),
})
There are two steps to dependency injection, creating a record of dependency factories, then creating a container that uses the factories to construct implementations.
When creating a record of dependency factories, the keys match the dependency
properties, and the values are functions that produce a dependency. The factory
functions may accept a single argument indicating any dependencies required for
the implementation. Wrap the record in a call to validateDependencyFactories
which is a compile-time-only check that ensures the dependency graph is sound.
That is, there are no cyclic dependencies, missing dependencies, or incompatible
types.
Example:
import { validateDependencyFactories } from '@amzn/vinyl-util'
class A {}
type BDependencies = {
readonly a: A
}
class B {
constructor(deps: BDependencies) {}
}
type CDependencies = {
readonly a: A
readonly b: B
}
class C {
constructor(deps: CDependencies) {}
}
const factories = validateDependencyFactories({
a: () => new A(),
b: (deps: BDependencies) => new B(deps),
c: (deps: CDependencies) => new C(deps),
} as const)
These factories will be used to create implementations. Use
createDependenciesContainer to create a container that may be disposed and
provides an object with getters to lazily-construct implementations from the
provided factories.
import { createDependenciesContainer, createDisposer } from '@amzn/vinyl-util'
const { add, dispose } = createDisposer()
const deps = add(createDependenciesContainer(factories)).dependencies
deps.a // A {}
deps.b // B {}
deps.c // C {}
As a general guideline, when defining a dependency to be provided, the interface requested should be the minimum API needed, and disposal should be the responsibility of the creator.