@6pm/depend
声明式依赖注入。
Table of contents
Install
npm install @6pm/depend --save
Test
运行单元测试套件。
git clone https://github.com/6pm-js/depend.git
cd depend
npm install
npm test
运行 nyc 覆盖率测试。
git clone https://github.com/6pm-js/depend.git
cd depend
npm install
npm run cover
这将导致文本摘要显示和详细的 html 覆盖
在 /coverage
中创建的报告。
Usage
使用需要两个不同的组件,依赖声明和
注入点 - 具体实现和创建的运行时绑定
实例/满足依赖性。
Declarative vs imperative
为什么支持两个非标准的 ES 扩展? 两个类属性,和
装饰器处于不断变化的状态,在即将到来的 ES 规范中还不稳定
(在撰写本文时),但有可用的实现,并且
关于声明式方法,有一些非常可读的东西。
命令式机制允许与现有的向后兼容集成
库,或者更安全路径,如果需要的话——但是声明式的方法是
个人喜好,因为我发现它不那么令人惊讶,而且更多
明确的意图。
不需要工具来允许声明性的装饰器方法,
除非你打算在你的项目中使用它,并且没有能力是独一无二的
无论哪种方法 - 选择您喜欢的任何一种。
为了使用声明式方法(在撰写本文时),两个
需要通过 Babel 转译进行转换:
Declaring dependencies
使用声明性装饰器语法
import { Inject } from '@6pm/depend';
import { InjectedType } from 'wherever'
class SomeClass {
@Inject(InjectedType)
property = null;
}
或命令式语法声明依赖项:
import { Depend } from '@6pm/depend';
import { InjectedType } from 'wherever'
class SomeClass {}
Depend.inject(SomeClass, 'property', InjectedType);
依赖项可以附加到整个类,在这种情况下,它们适用于
构造函数,在通过 a 创建时向其提供参数
Container
:
import { Inject, Depend } from '@6pm/depend';
import { InjectedTypeA, InjectedTypeB } from 'wherever'
// Declarative approach
@Inject(InjectedType, InjectedTypeB)
class SomeClass1 {
constructor(injectedInstanceA, injectedInstanceB) {}
}
// Imperative approach
class SomeClass2 {
constructor(injectedInstanceA, injectedInstanceB) {}
}
Depend.inject(SomeClass, null, InjectedTypeA, InjectedTypeB);
// The ^null is important, it specifies that we're not targeting a property of
// SomeClass2, but the constructor.
遗留装饰器插件,用于 Babel,不允许装饰
直接构造函数——这就是为什么声明式方法必须装饰
类,而不是实际的 constructor
函数。
依赖项也可以直接注入到类方法中,这些方法被处理
作为 setter 函数:
import { Inject, Depend } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
// Declarative approach
class SomeClass1 {
@Inject(DatabaseAbstraction)
setDatabaseAbstraction(dbAPI) {}
}
// Imperative approach
class SomeClass2 {
setDatabaseAbstraction(dbAPI) {}
}
Depend.inject(SomeClass, 'setDatabaseAbstraction', DatabaseAbstraction);
Creating instances, and satisfying dependencies
一旦一个类被声明为由 depend 管理,它不应该被手动
使用 new
实例化,而是通过托管 Container
创建。 这
Container
确保声明的任何依赖项都得到满足,包括
返回实例之前的子依赖项。
import { Inject, Container } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
@Inject(DatabaseAbstraction)
class SomeClass {
constructor(databaseAbstraction) {
// This will be called prior to create, below, completing.
}
}
// A new instance of DatabaseAbstraction will be created, and provided to the
// SomeClass constructor, prior to the new SomeClass instance being returned.
let instance = new Container().create(SomeClass);
Runtime determination of concrete implementations
Container
类不仅仅创建实例,而且满足
dependencies - 它也可以用于指定要使用的替代实现
在运行时,当遇到特定的依赖类型时。
这是使用 Container
实例上的 .bind()
方法实现的。
import { Inject, Container } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
import { DatabaseMySQL } from './database-mysql.js'
@Inject(DatabaseAbstraction)
class SomeClass {
constructor(databaseAbstraction) {
// This will be called prior to create, below, completing.
}
}
// A new instance of DatabaseMySQL will be created, and provided to the
// SomeClass constructor, prior to the new SomeClass instance being returned.
let instance = new Container()
.bind(DatabaseAbstraction, DatabaseMySQL)
.create(SomeClass);
Post injection intialisation
每个类的单个函数可以标记为 init
函数,这将是
在新实例上完成所有依赖注入后执行
拥有的类,像这样:
import { Init } from '@6pm/depend';
class SomeClass {
@Init
someInitFunction() {
// This will only execute once a Container.created instance of SomeClass
// has been fully created, and all dependencies injected.
}
}
或者,命令式地:
import { Depend } from '@6pm/depend';
class SomeClass {
someInitFunction() {
// This will only execute once a Container.created instance of SomeClass
// has been fully created, and all dependencies injected.
}
}
Depend.init(SomeClass, 'someInitFunction');
Inheritance
依赖使用 ES2016 类格式继承,或者传统的
原型继承。 所以扩展托管类会自动赋予
所有要求,并确定超载要求。
class A {
@Inject(SomeType)
property;
}
class B extends A {}
class C extends A {
@Inject(AlternativeType)
property;
}
在上面的示例中,创建 B
的实例会将 SomeType
注入到
property
,作为继承的要求。 创建实例 C
将注入
AlternativeType
的实例,而不需要或创建
SomeType
的实例首先。
Typical phases of a dependency injected application
依赖注入应用程序的阶段通常是:
意图声明/依赖项
- Using
@Inject
, @Abstract
and @Singleton
to declare intent, and requirements.
- This typically occurs throughout the application, where ever components reside.
配置/绑定
- Creating a
Container
instance, and binding concrete implementations
- This typically occurs in a single setup file that applies configuration
for a given environment.
运行时使用
- Wherever new instances are required, the
Container
instance created in
the configuration / binding step is used to create new instances, and
automatically satisfy any required dependencies.
例如:
./service.js
;
import { Inject, Singleton } from '@6pm/depend';
class Service {
@Inject(Logger)
logger;
start() {
logger.log('Started!');
}
}
<代码>./interface/someLogger.js;
class Logger {
@Abstract
log(message) {}
}
<代码>./implementation/console-logger.js;
class ConcreteConsoleLogger {
log(message) {
console.log(message);
}
}
./configuration/container.js
import { Container } from '@6pm/depend';
import Logger from './interface/logger';
import ConcreteLogger from './implementationconsole-logger';
function configure() {
let container = new Container();
container.bind(Logger, ConcreteLogger);
return container;
}
export let container = configure();
main.js
import Service from './service.js';
import container from './configuration/container.js';
let service = container.create(Service);
service.start();
As Promised
某些依赖项在访问远程之前无法准备好注入
资源,是否获取配置,或连接,资源等
。为了促进这一点,而不引入令人惊讶的方差水平
在返回类型中,Promise
可能会从三个关键点返回
依赖注入生命周期:
- constructor
- injected property setter
- init
如果上述任何方法返回一个 Promise
那么 container.create
也会
返回一个解析为新实例的 Promise
,当且仅当有且
所有返回的 Promise
成功解析 - 或者拒绝第一个
拒绝。
如果上述方法的所有三个(或任意组合)都返回 Promise
,那么
他们保证在上面列出的顺序中解决,并在相同的顺序中拒绝
命令。
如果在单个类上声明了多个注入的 setter,并返回多个
Promise
,那么它们将无序地有效地同时发生
它们之间的保证,但它们将使用 Promise.all
之前进行同步
到正在执行的 init
函数。
Downstream dependencies as Promises
为了:
- Prevent surprise (and cascading requirements to handle Promises)
- Ensure control flow options remain the preserve of the application developer
返回 Promise
的下游依赖项 DO NOT 自动提升
Promise
的上游类型。
也就是说,如果类 A
声明了一个实例的依赖注入
B
类,而 B
是一个 Promise
,创建 A
的实例不会产生结果
在 Promise
中,除非它显式返回 Promise
,如
上一节。
上面说了,这样可以防止意外(具体实现C
是
绑定为运行时配置,并解析为 Promise
,其中 B
没有),
并确保应用程序员可以选择如何处理这样的
情况 - A
的依赖注入实例可能不需要下游
在创建之前从 Promise
中解析依赖项 -
自动这样做会在步骤之间创建自动同步性,
例如,防止处理 Promise
的解决能力,并触发
结果,异步地处理任何进一步的逻辑。
因此,处理此类下游依赖项留给
应用程序开发人员,以及解析为 Promise
的任何注入依赖项
只会传递 Promise
- 如果依赖注入应该等待
Promise
d 依赖的解析,可以手动级联,通过返回
来自前面提到的任何生命周期方法的 Promise
,或者使用
async
/ await
语法,如果可用的话,像这样:
import { Inject, Container } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
@Inject(DatabaseAbstraction)
class SomeService {
async constructor(databaseAbstraction) {
this.db = await databaseAbstraction;
return this;
}
@Init
init() {
// This method will only be called if the construction succeeded, and
// the `.db` property has successfully resolved, and is available.
}
}
async function startService() {
try{
let someService = await new Container()
// .bind() any runtime configuration here
.create(SomeService);
// someService will now exist, with a fully resolved `.db` property.
someService.start();
}catch(e) {
// someService failed to instantiate, the causal error (from
// DatabaseAbstraction, or the bound concrete implementation, thereof),
// will be caught here.
}
}
startService();
// Alternatively, using the raw `Promise` flow.
new Container()
// .bind() any runtime configuration here
.create(SomeService)
.then(function(someService) {
// Fully resolved `someService` instance now available to use, with a
// resolved `.db` property.
someService.start();
}).catch(function(error) {
// If the `DatabaseAbstraction` rejects for some reason, `someService`
// instantiation will fail, calling this function with the causal Error.
});
Singletons
除了提供类型的实例,并在
运行时,某些类可能被设计为共享,只需要一个
一个实例,应该传递给任何已声明的依赖项。 这个可以
通过使用声明式将一个类声明为单例来实现
装饰器,或命令式风格。
import { Singleton, Depend } from '@6pm/depend';
@Singleton
class A {}
class B {}
Depend.singleton(B);
任何声明依赖于 A
或 B
的类都只会实例化一个
实例,并且所有人都将收到该单个实例。
这对于注入常见的实现特别有用,例如
数据存储抽象(也就是说,在后台处理连接池),或者
一个配置的日志记录接口,或有争议的仲裁机制
资源,常见的应用程序配置等
Interfaces in a language without interfaces
。JavaScript 没有任何真正的接口概念,所以依赖库
提供了一种机制来允许对概念进行有效的近似。
方法或整个类可以通过装饰器或
命令式方法,如下所示:
import { Abstract, Depend } from '@6pm/depend';
@Abstract
class A {}
class B {
@Abstract
someMethod() {}
}
class C {}
Depend.abstract(C);
抽象属性自动授予所属类,如果一个方法
被声明为抽象的,并具有以下效果:
虽然依赖库和 JavaScript 不验证合约
接口被接受,这增加了一定程度的安全性,因为 bind
a 失败
具体实施将导致创建新实例失败,
是否应该在任何阶段将抽象类作为依赖项请求 - 这
确保在启动时快速失败 - 并有可能发现错误
在生产配置的集成测试期间配置。
Destructured injection
除了指定在依赖注入期间要解析的类型之外,
依赖库可以接受有限形式的解构赋值,提供
更复杂的结构,例如数组字面量和对象字面量,如下所示:
@Inject({ database: DatabaseAbstraction, logger: LoggerAbstraction })
class SomeClass {
constructor(options) {
this.database = options.database;
this.logger = options.logger;
}
}
将遍历和注入任何深度的数组和对象字面量定义
根据需要 - 这与 ES2016 解构分配特别好:
@Inject([ { sub: A }, [ B, C ] ])
class SomeClass {
constructor([ { sub:a }, [ b, c ] ]) {
// a, b and c receive instances of A, B and C respectively.
}
}
Cyclic dependencies
依赖库自动确定依赖项是否导致循环,两者
在声明依赖关系时以及绑定具体时
实现,如果发生这种情况将抛出 - 因为周期性
永远无法正确满足依赖关系。
如果 A
需要 B
的实例,则需要 C
的实例,
然后需要 A
,无法确定 satisfied 状态
循环链上的任何实例。
如果检测到循环,则会从依赖项中抛出一个 Error
声明(@Inject
或 Depend.inject),或导致的
bind` 调用
周期。
依赖库被设计成非常高效的,使用两种主要方法
此外,动态代码生成,特别是在 v8 上,增加了非常
启动开销很小。
Contributing
欢迎所有问题、请求、问题、PR、改进或仅仅是评论!
我所要求的只是尝试符合相当宽松的房屋风格,如果打开
拉请求!
@6pm/depend
Declarative dependency injection.
Table of contents
Install
npm install @6pm/depend --save
Test
To run the unit test suite.
git clone https://github.com/6pm-js/depend.git
cd depend
npm install
npm test
To run nyc coverage tests.
git clone https://github.com/6pm-js/depend.git
cd depend
npm install
npm run cover
This will result in a textual summary display, and a detailed html coverage
report created in /coverage
.
Usage
Usage requires two distinct components, declaration of dependencies and
injection points - and runtime binding of concrete implementations, and creation
of instances / satisfying of dependencies.
Declarative vs imperative
Why support two non-standard ES extensions? Both class properties, and
decorators are in a state of flux, not yet stable in any upcoming ES spec
(at the time of writing), but there are available implementations that work, and
there is something eminently readable about declarative approaches.
The imperative mechanism allows backwards compatible integration with existing
libraries, or the safer path, if desired - but the declarative approach is
a personal preference, as I find it considerably less surprising, and more
explicit about intent.
There is no need to intrument to allow the declarative, decorator approach,
unless you intend to use it within your project, and no capability is unique
to either approach - choose whichever you are comfortable with.
In order to use the declarative approach (at the time of writing), two
transforms are needed, via Babel transpilation:
Declaring dependencies
Dependencies are declared using either the declarative decorator syntax:
import { Inject } from '@6pm/depend';
import { InjectedType } from 'wherever'
class SomeClass {
@Inject(InjectedType)
property = null;
}
or imperative syntax:
import { Depend } from '@6pm/depend';
import { InjectedType } from 'wherever'
class SomeClass {}
Depend.inject(SomeClass, 'property', InjectedType);
Dependencies can be attached to the whole class, in which case they apply to the
constructor function, supplying the parameters to it, when created via a
Container
:
import { Inject, Depend } from '@6pm/depend';
import { InjectedTypeA, InjectedTypeB } from 'wherever'
// Declarative approach
@Inject(InjectedType, InjectedTypeB)
class SomeClass1 {
constructor(injectedInstanceA, injectedInstanceB) {}
}
// Imperative approach
class SomeClass2 {
constructor(injectedInstanceA, injectedInstanceB) {}
}
Depend.inject(SomeClass, null, InjectedTypeA, InjectedTypeB);
// The ^null is important, it specifies that we're not targeting a property of
// SomeClass2, but the constructor.
The legacy decorator plugin, for Babel, does not allow decoration of
constructors directly - which is why the declarative approach must decorate the
class, rather than the actual constructor
function.
Dependencies can also be injected directly into class methods, which are treated
as setter functions:
import { Inject, Depend } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
// Declarative approach
class SomeClass1 {
@Inject(DatabaseAbstraction)
setDatabaseAbstraction(dbAPI) {}
}
// Imperative approach
class SomeClass2 {
setDatabaseAbstraction(dbAPI) {}
}
Depend.inject(SomeClass, 'setDatabaseAbstraction', DatabaseAbstraction);
Creating instances, and satisfying dependencies
Once a class is declared as managed by depend, it should not be manually
instantiated with new
, but instead created via a managed Container
. The
Container
ensures that any dependencies declared are satisified, including
sub-dependencies before returning an instance.
import { Inject, Container } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
@Inject(DatabaseAbstraction)
class SomeClass {
constructor(databaseAbstraction) {
// This will be called prior to create, below, completing.
}
}
// A new instance of DatabaseAbstraction will be created, and provided to the
// SomeClass constructor, prior to the new SomeClass instance being returned.
let instance = new Container().create(SomeClass);
Runtime determination of concrete implementations
The Container
class does much more than just create instances, and satisfy
dependencies - it can also be used to specify alternate implementations to use
at runtime, when a particular dependency type is encountered.
This is achieved using the .bind()
method on the Container
instance.
import { Inject, Container } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
import { DatabaseMySQL } from './database-mysql.js'
@Inject(DatabaseAbstraction)
class SomeClass {
constructor(databaseAbstraction) {
// This will be called prior to create, below, completing.
}
}
// A new instance of DatabaseMySQL will be created, and provided to the
// SomeClass constructor, prior to the new SomeClass instance being returned.
let instance = new Container()
.bind(DatabaseAbstraction, DatabaseMySQL)
.create(SomeClass);
Post injection intialisation
A single function per class may be marked as an init
function, which will be
executed after all dependency injection has been completed on a new instance of
the owning class, like so:
import { Init } from '@6pm/depend';
class SomeClass {
@Init
someInitFunction() {
// This will only execute once a Container.created instance of SomeClass
// has been fully created, and all dependencies injected.
}
}
or, imperatively:
import { Depend } from '@6pm/depend';
class SomeClass {
someInitFunction() {
// This will only execute once a Container.created instance of SomeClass
// has been fully created, and all dependencies injected.
}
}
Depend.init(SomeClass, 'someInitFunction');
Inheritance
Dependencies are inherited using the ES2016 class format, or traditional
prototypical inheritance. So extending a managed class automatically confers
all requirements, and determines overloaded requirements, too.
class A {
@Inject(SomeType)
property;
}
class B extends A {}
class C extends A {
@Inject(AlternativeType)
property;
}
In the above example, creating an instance of B
will inject SomeType
into
property
, as an inherited requirement. Creating an instance C
will inject
an instance of AlternativeType
, instead, without requiring, or creating an
instance of SomeType
first.
Typical phases of a dependency injected application
The phases of a dependency injected application are typically:
As an example:
./service.js
;
import { Inject, Singleton } from '@6pm/depend';
class Service {
@Inject(Logger)
logger;
start() {
logger.log('Started!');
}
}
./interface/someLogger.js
;
class Logger {
@Abstract
log(message) {}
}
./implementation/console-logger.js
;
class ConcreteConsoleLogger {
log(message) {
console.log(message);
}
}
./configuration/container.js
import { Container } from '@6pm/depend';
import Logger from './interface/logger';
import ConcreteLogger from './implementationconsole-logger';
function configure() {
let container = new Container();
container.bind(Logger, ConcreteLogger);
return container;
}
export let container = configure();
main.js
import Service from './service.js';
import container from './configuration/container.js';
let service = container.create(Service);
service.start();
As Promised
Some dependencies cannot be ready for injection until they have accessed remote
resources, whether to obtain configuration, or connections, resources, etc.
In order to facilitate this, without introducing surprising levels of variance
in return types, Promise
s may be returned from three key points within the
dependency injection lifecycle:
- constructor
- injected property setter
- init
If any of the above methods return a Promise
then container.create
will also
return a Promise
that resolves to the new instance if, and only if any and
all Promise
s returned resolve successfully - or reject on the first that
rejects.
If all three (or any combination) of the above methods return Promise
s, then
they are guaranteed to resolve in the oder listed above, and reject in the same
order.
If multiple injected setters are declared on a single class, and return multiple
Promise
s, then they will occur, effectively, simultaneously, with no order
guarantees between them, but they will be sychronised using Promise.all
prior
to an init
function being executed.
Downstream dependencies as Promises
In order to:
- Prevent surprise (and cascading requirements to handle Promises)
- Ensure control flow options remain the preserve of the application developer
Downstream dependencies that return Promise
s DO NOT automatically promote
an upstream type to a Promise
.
That is, if, say, class A
declares a dependency injection of an instance of
class B
, and B
is a Promise
, creating an instance of A
will not result
in a Promise
, unless it explicitly returns a Promise
as described in the
previous section.
As mentioned above, this prevents surprise (concrete implementation C
is
bound as runtime configuration, and resolves to a Promise
, where B
did not),
and ensures that the application programmer can choose how to handle such a
situation - the dependency injected instance of A
may not need the downstream
dependency to be resolved from a Promise
prior to being created -
automatically doing so would create an automatic synchronicity between steps,
preventing, say, the ability to handle resolving of a Promise
, and triggering
of any further logic as a consequence, asynchronously.
Handling of such downstream dependencies is therefore left as an exercise to the
application developer, and any injected dependencies that resolve to a Promise
will merely deliver the Promise
- if dependency injection should wait for
resolution of a Promise
d dependency, that can cascaded manually, by returning
a Promise
from any of the previously mentioned lifecycle methods, or, using
async
/ await
syntax, if available, like so:
import { Inject, Container } from '@6pm/depend';
import { DatabaseAbstraction } from './database.js'
@Inject(DatabaseAbstraction)
class SomeService {
async constructor(databaseAbstraction) {
this.db = await databaseAbstraction;
return this;
}
@Init
init() {
// This method will only be called if the construction succeeded, and
// the `.db` property has successfully resolved, and is available.
}
}
async function startService() {
try{
let someService = await new Container()
// .bind() any runtime configuration here
.create(SomeService);
// someService will now exist, with a fully resolved `.db` property.
someService.start();
}catch(e) {
// someService failed to instantiate, the causal error (from
// DatabaseAbstraction, or the bound concrete implementation, thereof),
// will be caught here.
}
}
startService();
// Alternatively, using the raw `Promise` flow.
new Container()
// .bind() any runtime configuration here
.create(SomeService)
.then(function(someService) {
// Fully resolved `someService` instance now available to use, with a
// resolved `.db` property.
someService.start();
}).catch(function(error) {
// If the `DatabaseAbstraction` rejects for some reason, `someService`
// instantiation will fail, calling this function with the causal Error.
});
Singletons
In addition to supplying instances of types, and binding them dynamically at
runtime, certain classes may be designed to be shared, requiring one, and only
one instance, which should to be passed to any declared dependency. This can
be achieved by declaring a class as a singleton, using either a declarative
decorator, or imperative style.
import { Singleton, Depend } from '@6pm/depend';
@Singleton
class A {}
class B {}
Depend.singleton(B);
Any class declaring a dependency of A
, or B
will only instantiate a single
instance, and all will receive that single instance.
This is particularly useful for injecting common implementations, such as a
datastore abstraction (that, say, handles connection pooling under the hood), or
a configured logging interface, or arbitration mechanism for contentious
resources, common application configuration, etc.
Interfaces in a language without interfaces
JavaScript does not have any real concept of interfaces, so the depend library
provides a mechanism to allow an effective approximation of the concept.
Methods, or entire classes, can be declared as abstract via the decorator or
imperative approach, as so:
import { Abstract, Depend } from '@6pm/depend';
@Abstract
class A {}
class B {
@Abstract
someMethod() {}
}
class C {}
Depend.abstract(C);
The abstract property is automatically conferred to the owning class, if a method
is declared abstract, and has the following effects:
For methods - any attempt to invoke the method will throw an Error
that the
method is abstract, and should be overloaded in a subclass.
For classes - any attempt to create
an instance of the class, via a container,
will throw an Error
indicating that the class is abstract, and a concrete
extension of the class should be used instead.
While the depend library, and JavaScript do not validate that the contract of
an interface is honoured, this adds a degree of safety, as a failure to bind
a
concrete implementation will result in the creation of a new instance failing,
should an abstract class be requested at any stage as a dependency - which
ensures fast failure, at startup - and the potential to catch an erroneous
configuration during an integration test of production configuration.
Destructured injection
In addition to specifying types to be resolved during dependency injection, the
depend library can accept a limited form of destructured assignment, providing
more complex structures, such as array literals, and object literals, like so:
@Inject({ database: DatabaseAbstraction, logger: LoggerAbstraction })
class SomeClass {
constructor(options) {
this.database = options.database;
this.logger = options.logger;
}
}
Any depth of array and object literal definitions will be traversed and injected
as needed - which works particularly well with ES2016 destructured assignment:
@Inject([ { sub: A }, [ B, C ] ])
class SomeClass {
constructor([ { sub:a }, [ b, c ] ]) {
// a, b and c receive instances of A, B and C respectively.
}
}
Cyclic dependencies
The depend library automatically determines if dependencies cause cycles, both
at the point of declaration of dependencies, and when binding concrete
implementations, and will throw if such a situation occurs - since cyclical
dependencies can never be correctly satisfied.
If A
requires an instance of B
, that then requires and instance of C
,
that then requires A
, there is no way to determine a satisfied state for
any instance along the cyclical chain.
If a cycle is detected, an Error
is thrown from either the dependency
declaration (the @Inject
or Depend.inject), or the
bind` call that causes
the cycle.
The depend library is designed to be very performant, using two primary methods:
Front loading as much work as possible, to ensure that creating, and satisfying
new instances is as cheap as possible. In particular, cycle detection occurs
during the declaration of dependencies, and the container binding stage -
ensuring that the cost does not affect run time performance, and usage patterns.
Dynamic code generation, to allow the ES runtime / compiler to optimise away
as much work as possible - while a contentious technique, this is never visible
externally, but testing during development, (via microbenchmarks - YMMV), proved
to provide more than 3 orders of magnitude difference in instance creation.
In addition, the dynamic code generation, on v8 in particular, adds very
little startup overhead.
Contributing
All issues, requests, questions, PRs, improvements, or merely comments welcome!
All I ask is to attempt to conform to the rather loose house style, if opening a
pull request!