Skip to content

Commit

Permalink
feat: impl MultiInstance inject MultiInstance (#240)
Browse files Browse the repository at this point in the history
<!--
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 support for multi-instance prototypes, enhancing the
module's capability to manage multiple instances of classes.
- Added new classes (`MultiInstanceProtoNode`, `BizManager`, `Secret`,
`App`, `App2`) to facilitate dependency injection and configuration
management.
- Implemented new methods for handling multi-instance logic and
retrieving class instances.

- **Bug Fixes**
	- Improved lifecycle management for multi-instance hooks.

- **Tests**
- Added comprehensive test cases to validate the functionality of
multi-instance injection and module management.

- **Documentation**
- Updated configuration files and module structures to reflect the new
multi-instance capabilities.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
killagu authored Sep 29, 2024
1 parent 96c9241 commit 08e3b0c
Show file tree
Hide file tree
Showing 36 changed files with 721 additions and 45 deletions.
24 changes: 12 additions & 12 deletions core/metadata/src/impl/LoadUnitMultiInstanceProtoHook.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { PrototypeUtil } from '@eggjs/core-decorator';
import type { EggProtoImplClass, LifecycleHook, LoadUnit, LoadUnitLifecycleContext } from '@eggjs/tegg-types';
import { EggPrototypeCreatorFactory } from '../factory/EggPrototypeCreatorFactory';
import { EggPrototypeFactory } from '../factory/EggPrototypeFactory';
// import { EggPrototypeCreatorFactory } from '../factory/EggPrototypeCreatorFactory';
// import { EggPrototypeFactory } from '../factory/EggPrototypeFactory';

export class LoadUnitMultiInstanceProtoHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {
multiInstanceClazzSet: Set<EggProtoImplClass> = new Set();
static multiInstanceClazzSet: Set<EggProtoImplClass> = new Set();

async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
const clazzList = ctx.loader.load();
const multiInstanceClazzList = Array.from(this.multiInstanceClazzSet);
static setAllClassList(clazzList: readonly EggProtoImplClass[]) {
for (const clazz of clazzList) {
if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {
this.multiInstanceClazzSet.add(clazz);
}
}
for (const clazz of multiInstanceClazzList) {
const protos = await EggPrototypeCreatorFactory.createProto(clazz, loadUnit);
for (const proto of protos) {
EggPrototypeFactory.instance.registerPrototype(proto, loadUnit);
}
}
}

static clear() {
this.multiInstanceClazzSet.clear();
}

async preCreate(): Promise<void> {
// ...
}
}

149 changes: 128 additions & 21 deletions core/metadata/src/impl/ModuleLoadUnit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { EggPrototypeFactory } from '../factory/EggPrototypeFactory';
import { LoadUnitFactory } from '../factory/LoadUnitFactory';
import { EggPrototypeCreatorFactory } from '../factory/EggPrototypeCreatorFactory';
import { MultiPrototypeFound } from '../errors';
import { LoadUnitMultiInstanceProtoHook } from './LoadUnitMultiInstanceProtoHook';

let id = 0;

Expand Down Expand Up @@ -64,21 +65,77 @@ class ProtoNode implements GraphNodeObj {
}
}

class MultiInstanceProtoNode implements GraphNodeObj {
readonly clazz: EggProtoImplClass;
readonly name: EggPrototypeName;
readonly id: string;
readonly qualifiers: QualifierInfo[];
readonly initType: ObjectInitTypeLike;
readonly unitPath: string;
readonly moduleName: string;

constructor(clazz: EggProtoImplClass, objName: EggPrototypeName, unitPath: string, moduleName: string) {
this.name = objName;
this.id = '' + (id++);
this.clazz = clazz;
this.qualifiers = QualifierUtil.getProtoQualifiers(clazz);
this.initType = PrototypeUtil.getInitType(clazz, {
unitPath,
moduleName,
})!;
this.unitPath = unitPath;
this.moduleName = moduleName;
}

verifyQualifiers(qualifiers: QualifierInfo[]): boolean {
const property = PrototypeUtil.getMultiInstanceProperty(this.clazz, {
unitPath: this.unitPath,
moduleName: this.moduleName,
});
if (!property) {
return false;
}
for (const obj of property.objects) {
const selfQualifiers = [
...this.qualifiers,
...obj.qualifiers,
];
if (this.verifyInstanceQualifiers(selfQualifiers, qualifiers)) {
return true;
}
}
return false;
}

verifyInstanceQualifiers(selfQualifiers: QualifierInfo[], qualifiers: QualifierInfo[]): boolean {
for (const qualifier of qualifiers) {
if (!selfQualifiers.find(t => t.attribute === qualifier.attribute && t.value === qualifier.value)) {
return false;
}
}
return true;
}

toString(): string {
return `${this.clazz.name}@${PrototypeUtil.getFilePath(this.clazz)}`;
}
}

export class ModuleGraph {
private graph: Graph<ProtoNode>;
private graph: Graph<ProtoNode | MultiInstanceProtoNode>;
clazzList: EggProtoImplClass[];
readonly unitPath: string;
readonly name: string;

constructor(clazzList: EggProtoImplClass[], unitPath: string, name: string) {
this.clazzList = clazzList;
this.graph = new Graph<ProtoNode>();
this.graph = new Graph<ProtoNode | MultiInstanceProtoNode>();
this.unitPath = unitPath;
this.name = name;
this.build();
}

private findInjectNode(objName: EggPrototypeName, qualifiers: QualifierInfo[], parentInitTye: ObjectInitTypeLike): GraphNode<ProtoNode> | undefined {
private findInjectNode(objName: EggPrototypeName, qualifiers: QualifierInfo[], parentInitTye: ObjectInitTypeLike): GraphNode<ProtoNode | MultiInstanceProtoNode> | undefined {
let nodes = Array.from(this.graph.nodes.values())
.filter(t => t.val.name === objName)
.filter(t => t.val.verifyQualifiers(qualifiers));
Expand All @@ -94,7 +151,16 @@ export class ModuleGraph {
value: parentInitTye,
};

nodes = nodes.filter(t => t.val.verifyQualifier(initTypeQualifier));
nodes = nodes.filter(t => t.val.verifyQualifiers([ initTypeQualifier ]));
if (nodes.length === 1) {
return nodes[0];
}

const temp: Map<EggProtoImplClass, GraphNode<ProtoNode | MultiInstanceProtoNode>> = new Map();
for (const node of nodes) {
temp.set(node.val.clazz, node);
}
nodes = Array.from(temp.values());
if (nodes.length === 1) {
return nodes[0];
}
Expand All @@ -104,14 +170,18 @@ export class ModuleGraph {
}

private build() {
const protoGraphNodes: GraphNode<ProtoNode>[] = [];
const protoGraphNodes: GraphNode<ProtoNode | MultiInstanceProtoNode>[] = [];
for (const clazz of this.clazzList) {
const objNames = PrototypeUtil.getObjNames(clazz, {
unitPath: this.unitPath,
moduleName: this.name,
});
for (const objName of objNames) {
protoGraphNodes.push(new GraphNode(new ProtoNode(clazz, objName, this.unitPath, this.name)));
if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {
protoGraphNodes.push(new GraphNode(new MultiInstanceProtoNode(clazz, objName, this.unitPath, this.name)));
} else {
protoGraphNodes.push(new GraphNode(new ProtoNode(clazz, objName, this.unitPath, this.name)));
}
}
}
for (const node of protoGraphNodes) {
Expand All @@ -120,13 +190,34 @@ export class ModuleGraph {
}
}
for (const node of protoGraphNodes) {
const injectObjects = PrototypeUtil.getInjectObjects(node.val.clazz);
for (const injectObject of injectObjects) {
const qualifiers = QualifierUtil.getProperQualifiers(node.val.clazz, injectObject.refName);
const injectNode = this.findInjectNode(injectObject.objName, qualifiers, node.val.initType);
// If not found maybe in other module
if (injectNode) {
this.graph.addEdge(node, injectNode);
if (PrototypeUtil.isEggMultiInstancePrototype(node.val.clazz)) {
const property = PrototypeUtil.getMultiInstanceProperty(node.val.clazz, {
moduleName: this.name,
unitPath: this.unitPath,
});
for (const objectInfo of property?.objects || []) {
const injectObjects = PrototypeUtil.getInjectObjects(node.val.clazz);
for (const injectObject of injectObjects) {
const qualifiers = [
...QualifierUtil.getProperQualifiers(node.val.clazz, injectObject.refName),
...objectInfo.properQualifiers?.[injectObject.refName] ?? [],
];
const injectNode = this.findInjectNode(injectObject.objName, qualifiers, node.val.initType);
// If not found maybe in other module
if (injectNode) {
this.graph.addEdge(node, injectNode);
}
}
}
} else {
const injectObjects = PrototypeUtil.getInjectObjects(node.val.clazz);
for (const injectObject of injectObjects) {
const qualifiers = QualifierUtil.getProperQualifiers(node.val.clazz, injectObject.refName);
const injectNode = this.findInjectNode(injectObject.objName, qualifiers, node.val.initType);
// If not found maybe in other module
if (injectNode) {
this.graph.addEdge(node, injectNode);
}
}
}
}
Expand Down Expand Up @@ -162,8 +253,9 @@ export class ModuleLoadUnit implements LoadUnit {
this.loader = loader;
}

private loadClazz(): EggProtoImplClass[] {
private doLoadClazz(): EggProtoImplClass[] {
const clazzList = this.loader.load();
const result = clazzList.slice();
for (const clazz of clazzList) {
const defaultQualifier = [{
attribute: InitTypeQualifierAttribute,
Expand All @@ -179,12 +271,30 @@ export class ModuleLoadUnit implements LoadUnit {
QualifierUtil.addProtoQualifier(clazz, qualifier.attribute, qualifier.value);
});
}
return clazzList;
for (const clazz of LoadUnitMultiInstanceProtoHook.multiInstanceClazzSet) {
const instanceProperty = PrototypeUtil.getMultiInstanceProperty(clazz, {
moduleName: this.name,
unitPath: this.unitPath,
});
if (instanceProperty) {
result.push(clazz);
}
}
return result;
}

private loadClazz() {
if (!this.clazzList) {
const clazzList = this.doLoadClazz();
const protoGraph = new ModuleGraph(clazzList, this.unitPath, this.name);
protoGraph.sort();
this.clazzList = protoGraph.clazzList;
}
}

async preLoad() {
const clazzList = this.loader.load();
for (const protoClass of clazzList) {
this.loadClazz();
for (const protoClass of this.clazzList) {
const fnName = LifecycleUtil.getStaticLifecycleHook('preLoad', protoClass);
if (fnName) {
await protoClass[fnName]?.();
Expand All @@ -193,10 +303,7 @@ export class ModuleLoadUnit implements LoadUnit {
}

async init() {
const clazzList = this.loadClazz();
const protoGraph = new ModuleGraph(clazzList, this.unitPath, this.name);
protoGraph.sort();
this.clazzList = protoGraph.clazzList;
this.loadClazz();
for (const clazz of this.clazzList) {
const protos = await EggPrototypeCreatorFactory.createProto(clazz, this);
for (const proto of protos) {
Expand Down
54 changes: 46 additions & 8 deletions core/metadata/src/model/AppGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { EggProtoImplClass, EggPrototypeName, GraphNodeObj, ModuleReference
export interface InstanceClazzMeta {
name: PropertyKey;
qualifiers: QualifierInfo[];
properQualifiers: Record<PropertyKey, QualifierInfo[]>;
accessLevel: AccessLevel,
instanceModule: GraphNode<ModuleNode>;
ownerModule: GraphNode<ModuleNode>;
Expand Down Expand Up @@ -65,6 +66,7 @@ export class ClazzMap {
...qualifiers,
...info.qualifiers,
],
properQualifiers: info.properQualifiers || {},
instanceModule: instanceNode,
ownerModule: ownerNode,
});
Expand All @@ -81,6 +83,7 @@ export class ClazzMap {
moduleName: ownerNode.val.moduleConfig.name,
}) as AccessLevel,
qualifiers,
properQualifiers: {},
ownerModule: ownerNode,
instanceModule: ownerNode,
});
Expand Down Expand Up @@ -153,7 +156,7 @@ export class ClazzMap {

for (const obj of mayObjs) {
result.add(obj.instanceModule);
result.add(obj.ownerModule);
// result.add(obj.ownerModule);
}
return Array.from(result);
}
Expand Down Expand Up @@ -215,6 +218,16 @@ export class AppGraph {
}
}

getClazzList(): readonly EggProtoImplClass[] {
const clazzSet = new Set<EggProtoImplClass>();
for (const node of this.graph.nodes.values()) {
for (const clazz of node.val.getClazzList()) {
clazzSet.add(clazz);
}
}
return Array.from(clazzSet);
}

build() {
this.clazzMap = new ClazzMap(this.graph);

Expand All @@ -225,13 +238,38 @@ export class AppGraph {
const injectObjects = PrototypeUtil.getInjectObjects(clazz);
// 3. iterate all inject objects
for (const injectObject of injectObjects) {
const properQualifiers = QualifierUtil.getProperQualifiers(clazz, injectObject.refName);
// 4. find dependency module
const dependencyModules = this.clazzMap.findDependencyModule(injectObject.objName, properQualifiers, node);
for (const moduleNode of dependencyModules) {
// 5. add edge
if (node !== moduleNode) {
this.graph.addEdge(node, moduleNode);
if (PrototypeUtil.isEggMultiInstancePrototype(clazz)) {
for (const instanceNode of this.graph.nodes.values()) {
const property = PrototypeUtil.getMultiInstanceProperty(clazz, {
unitPath: instanceNode.val.moduleConfig.path,
moduleName: instanceNode.val.moduleConfig.name,
});
for (const info of property?.objects || []) {
const properQualifiers = [
...QualifierUtil.getProperQualifiers(clazz, injectObject.refName),
...info.properQualifiers?.[injectObject.refName] ?? [],
];
// 4. find dependency module
const dependencyModules = this.clazzMap.findDependencyModule(injectObject.objName, properQualifiers, node);
for (const moduleNode of dependencyModules) {
// 5. add edge
if (instanceNode !== moduleNode) {
this.graph.addEdge(instanceNode, moduleNode);
}
}
}
}
} else {
const properQualifiers = [
...QualifierUtil.getProperQualifiers(clazz, injectObject.refName),
];
// 4. find dependency module
const dependencyModules = this.clazzMap.findDependencyModule(injectObject.objName, properQualifiers, node);
for (const moduleNode of dependencyModules) {
// 5. add edge
if (node !== moduleNode) {
this.graph.addEdge(node, moduleNode);
}
}
}
}
Expand Down
Loading

0 comments on commit 08e3b0c

Please sign in to comment.