Skip to content

Commit

Permalink
feat: impl MultiInstanceInfo decorator (#239)
Browse files Browse the repository at this point in the history
Add multi instances info for constructor inject mode.

<!--
Thank you for your pull request. Please review below requirements.
Bug fixes and new features should include tests and possibly benchmarks.
Contributors guide:
https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md

感谢您贡献代码。请确认下列 checklist 的完成情况。
Bug 修复和新功能必须包含测试,必要时请附上性能测试。
Contributors guide:
https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md
-->

##### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to
[x]. -->

- [ ] `npm test` passes
- [ ] tests and/or benchmarks are included
- [ ] documentation is changed or added
- [ ] commit message follows commit guidelines

##### Affected core subsystem(s)
<!-- Provide affected core subsystem(s). -->


##### Description of change
<!-- Provide a description of the change below this comment. -->

<!--
- any feature?
- close https://github.com/eggjs/egg/ISSUE_URL
-->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced the `MultiInstanceInfo` decorator for enhanced
multi-instance management.
- Added support for multi-instance constructor attributes and indexing
in relevant classes.
- Enhanced `getEggObject` method to accept additional parameters for
improved flexibility.

- **Bug Fixes**
- Improved the construction logic for `EggObject` to handle
multi-instance information.

- **Tests**
- Added a test case to verify the loading of multiple instances using
the new constructor pattern.

- **Documentation**
- Updated interface definitions to include properties related to
multi-instance management.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
killagu committed Sep 29, 2024
1 parent 6cbdfc7 commit 70d4d95
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 7 deletions.
1 change: 1 addition & 0 deletions core/core-decorator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './src/decorator/ContextProto';
export * from './src/decorator/SingletonProto';
export * from './src/decorator/EggQualifier';
export * from './src/decorator/MultiInstanceProto';
export * from './src/decorator/MultiInstanceInfo';
export * from './src/decorator/ConfigSource';

export * from './src/util/MetadataUtil';
Expand Down
9 changes: 9 additions & 0 deletions core/core-decorator/src/decorator/MultiInstanceInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PrototypeUtil } from '../util/PrototypeUtil';
import { QualifierAttribute } from '@eggjs/tegg-types';

export function MultiInstanceInfo(attributes: QualifierAttribute[]) {
return function(target: any, _propertyKey: PropertyKey | undefined, parameterIndex: number) {
PrototypeUtil.setMultiInstanceConstructorIndex(target, parameterIndex);
PrototypeUtil.setMultiInstanceConstructorAttributes(target, attributes);
};
}
20 changes: 19 additions & 1 deletion core/core-decorator/src/util/PrototypeUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
InjectConstructorInfo,
InjectObjectInfo,
InjectType,
MultiInstancePrototypeGetObjectsContext,
MultiInstancePrototypeGetObjectsContext, QualifierAttribute,
} from '@eggjs/tegg-types';
import { MetadataUtil } from './MetadataUtil';

Expand All @@ -22,6 +22,8 @@ export class PrototypeUtil {
static readonly INJECT_TYPE = Symbol.for('EggPrototype.injectType');
static readonly INJECT_CONSTRUCTOR_NAME_SET = Symbol.for('EggPrototype.injectConstructorNames');
static readonly CLAZZ_PROTO = Symbol.for('EggPrototype.clazzProto');
static readonly MULTI_INSTANCE_CONSTRUCTOR_INDEX = Symbol.for('EggPrototype#multiInstanceConstructorIndex');
static readonly MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES = Symbol.for('EggPrototype#multiInstanceConstructorAttributes');

/**
* Mark class is egg object prototype
Expand Down Expand Up @@ -146,6 +148,22 @@ export class PrototypeUtil {
}
}

static setMultiInstanceConstructorAttributes(clazz: EggProtoImplClass, attributes: QualifierAttribute[]) {
MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES, attributes, clazz);
}

static getMultiInstanceConstructorAttributes(clazz: EggProtoImplClass): QualifierAttribute[] {
return MetadataUtil.getMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_ATTRIBUTES, clazz) || [];
}

static setMultiInstanceConstructorIndex(clazz: EggProtoImplClass, index: number) {
MetadataUtil.defineMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_INDEX, index, clazz);
}

static getMultiInstanceConstructorIndex(clazz: EggProtoImplClass): number | undefined {
return MetadataUtil.getMetaData(PrototypeUtil.MULTI_INSTANCE_CONSTRUCTOR_INDEX, clazz);
}

static setInjectType(clazz: EggProtoImplClass, type: InjectType) {
const injectType: InjectType | undefined = MetadataUtil.getMetaData(PrototypeUtil.INJECT_TYPE, clazz);
if (!injectType) {
Expand Down
8 changes: 7 additions & 1 deletion core/metadata/src/impl/EggPrototypeBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from 'node:assert';
import { InjectType, PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator';
import { InjectType, PrototypeUtil, QualifierAttribute, QualifierUtil } from '@eggjs/core-decorator';
import type {
AccessLevel,
EggProtoImplClass,
Expand Down Expand Up @@ -35,6 +35,8 @@ export class EggPrototypeBuilder {
private loadUnit: LoadUnit;
private qualifiers: QualifierInfo[] = [];
private className?: string;
private multiInstanceConstructorIndex?: number;
private multiInstanceConstructorAttributes?: QualifierAttribute[];

static create(ctx: EggPrototypeLifecycleContext): EggPrototype {
const { clazz, loadUnit } = ctx;
Expand All @@ -54,6 +56,8 @@ export class EggPrototypeBuilder {
...QualifierUtil.getProtoQualifiers(clazz),
...(ctx.prototypeInfo.qualifiers ?? []),
];
builder.multiInstanceConstructorIndex = PrototypeUtil.getMultiInstanceConstructorIndex(clazz);
builder.multiInstanceConstructorAttributes = PrototypeUtil.getMultiInstanceConstructorAttributes(clazz);
return builder.build();
}

Expand Down Expand Up @@ -140,6 +144,8 @@ export class EggPrototypeBuilder {
this.qualifiers,
this.className,
this.injectType,
this.multiInstanceConstructorIndex,
this.multiInstanceConstructorAttributes,
);
}
}
Expand Down
8 changes: 7 additions & 1 deletion core/metadata/src/impl/EggPrototypeImpl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InjectType, MetadataUtil } from '@eggjs/core-decorator';
import { InjectType, MetadataUtil, QualifierAttribute } from '@eggjs/core-decorator';
import type {
AccessLevel,
EggProtoImplClass,
Expand Down Expand Up @@ -26,6 +26,8 @@ export class EggPrototypeImpl implements EggPrototype {
readonly injectType: InjectType;
readonly loadUnitId: Id;
readonly className?: string;
readonly multiInstanceConstructorIndex?: number;
readonly multiInstanceConstructorAttributes?: QualifierAttribute[];

constructor(
id: string,
Expand All @@ -39,6 +41,8 @@ export class EggPrototypeImpl implements EggPrototype {
qualifiers: QualifierInfo[],
className?: string,
injectType?: InjectType,
multiInstanceConstructorIndex?: number,
multiInstanceConstructorAttributes?: QualifierAttribute[],
) {
this.id = id;
this.clazz = clazz;
Expand All @@ -51,6 +55,8 @@ export class EggPrototypeImpl implements EggPrototype {
this.qualifiers = qualifiers;
this.className = className;
this.injectType = injectType || InjectType.PROPERTY;
this.multiInstanceConstructorIndex = multiInstanceConstructorIndex;
this.multiInstanceConstructorAttributes = multiInstanceConstructorAttributes;
}

verifyQualifiers(qualifiers: QualifierInfo[]): boolean {
Expand Down
20 changes: 18 additions & 2 deletions core/runtime/src/impl/EggObjectImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
EggObjectLifecycle,
EggObjectLifeCycleContext,
EggObjectName,
EggPrototype,
EggPrototype, ObjectInfo, QualifierInfo,
} from '@eggjs/tegg-types';
import { EggObjectStatus, InjectType, ObjectInitType } from '@eggjs/tegg-types';
import { IdenticalUtil } from '@eggjs/tegg-lifecycle';
Expand Down Expand Up @@ -92,7 +92,7 @@ export default class EggObjectImpl implements EggObject {
// 4. call obj lifecycle postCreate
// 5. success create
try {
const constructArgs = await Promise.all(this.proto.injectObjects!.map(async injectObject => {
const constructArgs: any[] = await Promise.all(this.proto.injectObjects!.map(async injectObject => {
const proto = injectObject.proto;
const loadUnit = LoadUnitFactory.getLoadUnitById(proto.loadUnitId);
if (!loadUnit) {
Expand All @@ -104,6 +104,22 @@ export default class EggObjectImpl implements EggObject {
const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);
return EggObjectUtil.eggObjectProxy(injectObj);
}));
if (typeof this.proto.multiInstanceConstructorIndex !== 'undefined') {
const qualifiers = this.proto.multiInstanceConstructorAttributes
?.map(t => {
return {
attribute: t,
value: this.proto.getQualifier(t),
} as QualifierInfo;
})
?.filter(t => typeof t.value !== 'undefined')
?? [];
const objInfo: ObjectInfo = {
name: this.proto.name,
qualifiers,
};
constructArgs.splice(this.proto.multiInstanceConstructorIndex, 0, objInfo);
}

this._obj = this.proto.constructEggObject(...constructArgs);
const objLifecycleHook = this._obj as EggObjectLifecycle;
Expand Down
38 changes: 38 additions & 0 deletions core/runtime/test/LoadUnitInstance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Bar, Foo } from './fixtures/modules/extends-module/Base';
import { ContextHandler } from '../src/model/ContextHandler';
import { EggContextStorage } from './fixtures/EggContextStorage';
import { FOO_ATTRIBUTE, FooLogger } from './fixtures/modules/multi-instance-module/MultiInstance';
import { FooLoggerConstructor } from './fixtures/modules/multi-instance-module/MultiInstanceConstructor';

describe('test/LoadUnit/LoadUnitInstance.test.ts', () => {
describe('ModuleLoadUnitInstance', () => {
Expand Down Expand Up @@ -101,6 +102,43 @@ describe('test/LoadUnit/LoadUnitInstance.test.ts', () => {

await TestUtil.destroyLoadUnitInstance(instance);
});

it('should load multi instance with constructor', async () => {
const instance = await TestUtil.createLoadUnitInstance('multi-instance-module');
const foo1Proto = EggPrototypeFactory.instance.getPrototype('fooConstructor', instance.loadUnit, [{
attribute: FOO_ATTRIBUTE,
value: 'foo1',
}]);
const foo1Obj = await EggContainerFactory.getOrCreateEggObject(foo1Proto, foo1Proto.name);
const foo1 = foo1Obj.obj as FooLoggerConstructor;

const foo2Proto = EggPrototypeFactory.instance.getPrototype('fooConstructor', instance.loadUnit, [{
attribute: FOO_ATTRIBUTE,
value: 'foo2',
}]);
const foo2Obj = await EggContainerFactory.getOrCreateEggObject(foo2Proto, foo2Proto.name);
const foo2 = foo2Obj.obj as FooLoggerConstructor;
assert(foo1);
assert(foo2);
assert(foo1 !== foo2);
assert(foo1.foo === 'foo1');
assert(foo2.foo === 'foo2');
assert(foo1.bar === 'bar');
assert(foo2.foo === 'foo2');

const obj1 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'fooConstructor', [{
attribute: FOO_ATTRIBUTE,
value: 'foo1',
}]);
const obj2 = await EggContainerFactory.getOrCreateEggObjectFromClazz(FooLogger, 'fooConstructor', [{
attribute: FOO_ATTRIBUTE,
value: 'foo2',
}]);
assert(foo1Obj === obj1);
assert(foo2Obj === obj2);

await TestUtil.destroyLoadUnitInstance(instance);
});
});

describe('MultiModule', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
AccessLevel,
ObjectInitType,
QualifierValue,
ObjectInfo
} from '@eggjs/tegg-types';
import { Inject, MultiInstanceInfo, MultiInstanceProto, SingletonProto } from '@eggjs/core-decorator';

export const FOO_ATTRIBUTE = Symbol.for('FOO_ATTRIBUTE');

@SingletonProto()
export class Bar {
bar = 'bar';
}

@MultiInstanceProto({
accessLevel: AccessLevel.PUBLIC,
initType: ObjectInitType.SINGLETON,
objects: [{
name: 'fooConstructor',
qualifiers: [{
attribute: FOO_ATTRIBUTE,
value: 'foo1',
}],
}, {
name: 'fooConstructor',
qualifiers: [{
attribute: FOO_ATTRIBUTE,
value: 'foo2',
}],
}],
})
export class FooLoggerConstructor {
foo: QualifierValue | undefined;
bar: string;

constructor(@Inject() bar: Bar, @MultiInstanceInfo([ FOO_ATTRIBUTE ]) objInfo: ObjectInfo) {
this.foo = objInfo.qualifiers.find(t => t.attribute === FOO_ATTRIBUTE)?.value;
this.bar = bar.bar;
}
}
2 changes: 2 additions & 0 deletions core/types/metadata/model/EggPrototype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export interface EggPrototype extends LifecycleObject<EggPrototypeLifecycleConte
readonly injectObjects: Array<InjectObjectProto | InjectConstructorProto>;
readonly injectType?: InjectType;
readonly className?: string;
readonly multiInstanceConstructorIndex?: number;
readonly multiInstanceConstructorAttributes?: QualifierAttribute[];

/**
* get metedata for key
Expand Down
4 changes: 2 additions & 2 deletions plugin/tegg/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ declare module 'egg' {
// 兼容现有 module 的定义
module: EggModule & EggApplicationModule;

getEggObject<T>(clazz: EggProtoImplClass<T>): Promise<T>;
getEggObject<T>(clazz: EggProtoImplClass<T>, name?: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T>;
getEggObjectFromName<T>(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<unknown>;
}

Expand All @@ -68,7 +68,7 @@ declare module 'egg' {
// 兼容现有 module 的定义
module: EggModule & EggContextModule;

getEggObject<T>(clazz: EggProtoImplClass<T>): Promise<T>;
getEggObject<T>(clazz: EggProtoImplClass<T>, name?: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise<T>;
}

interface Application extends TEggApplication {
Expand Down

0 comments on commit 70d4d95

Please sign in to comment.