Beanโ
Bean is an object that is managed and constructed by Clawject container it can have dependencies, and it can be a dependency for other Beans.
Declare Beanโ
There are a few ways to declare a Bean, let's explore all of them one by one.
@Bean
decoratorโ
You can decorate property, getter or method with @Bean
decorator to declare a Bean.
When you're decorating method or property that holds arrow function - it's your responsibility to provide return value for bean, if it's class - it should be instantiated, if it's some dynamic value โ it should be returned from function provided.
Functions decorated with @Bean
decorator called factory-functions.
You can provide bean dependencies via function arguments by specifying an argument type.
class Foo {
name = 'foo';
}
@ClawjectApplication
class Application {
@Bean foo() {
return new Foo();
}
@Bean fooName = (foo: Foo) => foo.name;
}
When your bean doesn't have any dependencies -
you can just assign value to property decorated with @Bean
decorator.
class Foo {}
@ClawjectApplication
class Application {
@Bean foo = new Foo();
}
Bean
functionโ
It can be quite annoying to declare each dependency in method or arrow function for your class,
especially when your class has a lot of dependencies.
So, if you add/change/remove a dependency - you need to change it in factory-function level as well.
To solve this inconvenience Clawject has Bean
function that accepts class constructor,
and automatically instantiate it with all needed dependencies.
- Declaring bean with Bean function
- Declaring bean with @Bean decorator and method
class Bar {}
class Baz {}
class Foo {
constructor(
private dependency0: string,
private dependency1: number,
private dependency2: Bar,
private dependency3: Baz,
) {}
}
@ClawjectApplication
class Application {
foo = Bean(Foo);
@Bean stringBean = 'dependency0';
@Bean numberBean = 1;
barBean = Bean(Bar);
bazBean = Bean(Baz);
}
class Bar {}
class Baz {}
class Foo {
constructor(
private dependency0: string,
private dependency1: number,
private dependency2: Bar,
private dependency3: Baz,
) {}
}
@ClawjectApplication
class Application {
@Bean foo(
dependency0: string,
dependency1: number,
dependency2: Bar,
dependency3: Baz,
) {
return new Foo(
dependency0,
dependency1,
dependency2,
dependency3,
);
}
@Bean stringBean = 'dependency0';
@Bean numberBean = 1;
@Bean barBean() {
return new Bar();
}
@Bean bazBean() {
return new Baz();
}
}
Bean Typesโ
Bean type is a type of value that bean will provide as a dependency for other beans.
There are a few restrictions on bean value types: undefined
, void
, null
and never
types.
If you specify an invalid bean type, you will get compile-time error.
@ClawjectApplication
class Application {
@Bean undefined = undefined;
@Bean void: void = undefined;
@Bean null = null;
@Bean never(): never { throw new Error('never'); }
}
Specifying a Bean typeโ
You can specify the type explicitly for each Bean, but it is not necessary to, because Clawject using TypeScript type system to infer types.
The next two examples are equivalent:
- Infering type based on value
- Specify type explicitly
class Foo {}
@ClawjectApplication
class Application {
@Bean propertyBean = new Foo();
@Bean factoryMethodBean() { return new Foo(); }
@Bean arrowFunctionBean = () => new Foo();
functionBean = Bean(Foo);
}
@ClawjectApplication
class Application {
@Bean propertyBean: Foo = new Foo();
@Bean factoryMethodBean(): Foo { return new Foo(); }
@Bean arrowFunctionBean = (): Foo => new Foo();
functionBean = Bean<Foo, typeof Foo>(Foo);
}
Bean type narrowingโ
In this example we will narrow a bean type from FooBar & IFoo & IBar
to IFoo
:
interface IFoo {
foo: string;
}
interface IBar {
bar: string;
}
class FooBar implements IFoo, IBar {
foo = 'foo';
bar = 'bar';
}
@ClawjectApplication
class Application {
/* The type of bean is IFoo now */
fooBar = Bean<IFoo, typeof FooBar>(FooBar);
@PostConstruct
init(
/* fooBar bean will be injected */
foo: IFoo,
/* Compilation error here because no beans found with type IBar */
bar: IBar,
/* Compilation error here because no beans found with type FooBar */
fooBar: FooBar,
) {}
}
Asynchronous Beansโ
Sometimes you need to create a bean asynchronously, for example, when you need to fetch some data from the server to create a bean.
To mark beans as asynchronous, you can just return Promise
from factory-function, async
method or just plain bean value.
When Bean is requested as a dependency for another bean or as an exposed bean, Clawject will wait for the promise to resolve and then use the resolved value as a bean.
class Foo {
constructor(
/*
* We don't need to specify parameter as Promise<string>
* because Clawject will handle promises automatically
*/
private somePropertyThatFetchedAsynchronously: string,
) {}
}
@ClawjectApplication
class Application {
@Bean async somePropertyThatFetchedAsynchronously() {
const response = await fetch('https://example.com/some-data');
return response.text();
}
foo = Bean(Foo);
}
Also if you want to use dynamic import to create a bean without factory functions (using only Bean
function) you can do it like this:
export class Foo {}
@ClawjectApplication
class Application {
foo = Bean(import('./foo').then(module => module.Foo));
}
Bean Dependenciesโ
Bean dependency is a parameter of a bean that is injected by Clawject.
Dependency can be declared in class constructor, or in a factory-method
.
- Class-Constructor Bean dependencies
- Factory-Method bean dependencies
class Foo {
constructor(
private bar: string,
private baz: number,
) {}
}
@ClawjectApplication
class Application {
@Bean bar = 'barValue';
@Bean baz = 42;
foo = Bean(Foo);
}
class Foo {
constructor(
private bar: string,
private baz: number,
) {}
}
@ClawjectApplication
class Application {
@Bean bar = 'barValue';
@Bean baz = 42;
@Bean foo(bar: string, baz: number) {
return new Foo(bar, baz)
}
}
How dependencies are resolvedโ
There are a few steps that are taken to resolve bean dependencies.
Firstly - Clawject try to find all beans that are compatible with a dependency type. If none of the compatible bean candidates are found - compilation error will be reported.
If only one compatible bean is found - it's used as a dependency.
class Foo {
constructor(
private bar: string // <- "barBean" will be injected
) {}
}
@ClawjectApplication
class Application {
@Bean barBean = 'barValue';
foo = Bean(Foo);
}
If more than one compatible bean is found - Clawject try to find a candidate that has the same name as parameter. If such a bean is found - it's used as a dependency.
class Foo {
constructor(
private bar: string // <- "bar" will be injected
) {}
}
@ClawjectApplication
class Application {
@Bean bar = 'barValue';
@Bean barOther = 'barOtherValue';
foo = Bean(Foo);
}
If more than one compatible bean is found, and none of them has the same name
as parameter and not @Primary
- then compilation error will be reported.
class Foo {
constructor(
private bar: string // <- compilation error
) {}
}
@ClawjectApplication
class Application {
@Bean bar1 = 'bar1Value';
@Bean bar2 = 'bar2Value';
foo = Bean(Foo);
}
Optional dependenciesโ
Dependencies can be optional, if there are no suitable bean candidates found - undefined
or null
will be injected.
Let's take a look at how you can mark dependency as optional:
- Optional operator
- Union with undefined
- Union with null
class Foo {
constructor(
private bar?: string, // <- optional dependency, `undefined` will be injected
) {}
}
@ClawjectApplication
class Application {
foo = Bean(Foo);
}
class Foo {
constructor(
private bar: string | undefined, // <- optional dependency, `undefined` will be injected
) {}
}
@ClawjectApplication
class Application {
foo = Bean(Foo);
}
class Foo {
constructor(
private bar: string | null, // <- optional dependency, `null` will be injected
) {}
}
@ClawjectApplication
class Application {
foo = Bean(Foo);
}
Circular dependenciesโ
Clawject will detect circular dependencies and report compilation error with involved beans.
class Foo {
constructor(
private bar: Bar,
) {}
}
class Bar {
constructor(
private baz: Baz,
) {}
}
class Baz {
constructor(
private foo: Foo,
) {}
}
@ClawjectApplication
class Application {
/*
* Circular dependencies detected.
* โโโโโโโ
* | foo
* โ โ
* | bar
* โ โ
* | baz
* โโโโโโโ
*/
foo = Bean(Foo);
bar = Bean(Bar);
baz = Bean(Baz);
}