Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add aop runtime/dynamic inject runtime for standalone #149

Merged
merged 1 commit into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion standalone/standalone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"@eggjs/tegg-lifecycle": "^3.17.0",
"@eggjs/tegg-loader": "^3.17.0",
"@eggjs/tegg-metadata": "^3.17.0",
"@eggjs/tegg-runtime": "^3.17.0"
"@eggjs/tegg-runtime": "^3.17.0",
"@eggjs/tegg-aop-runtime": "^3.17.0"
},
"publishConfig": {
"access": "public"
Expand Down
4 changes: 4 additions & 0 deletions standalone/standalone/src/ConfigSourceLoadUnitHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
} from '@eggjs/tegg';
import { ConfigSourceQualifier, ConfigSourceQualifierAttribute } from './ConfigSource';

/**
* Hook for inject moduleConfig.
* Add default qualifier value is current module name.
*/
export class ConfigSourceLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {
async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
const classList = ctx.loader.load();
Expand Down
26 changes: 26 additions & 0 deletions standalone/standalone/src/LoadUnitInnerClassHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { LifecycleHook } from '@eggjs/tegg-lifecycle';
import {
EggPrototypeFactory,
LoadUnit,
LoadUnitLifecycleContext,
EggPrototypeCreatorFactory,
} from '@eggjs/tegg-metadata';
import { EggProtoImplClass } from '@eggjs/tegg';
import { EggObjectFactory } from '@eggjs/tegg-dynamic-inject-runtime';

const INNER_CLASS_LIST = [
EggObjectFactory,
];

export class LoadUnitInnerClassHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {
async postCreate(_: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
if (loadUnit.type === 'StandaloneLoadUnitType') {
for (const clazz of INNER_CLASS_LIST) {
const protos = await EggPrototypeCreatorFactory.createProto(clazz as EggProtoImplClass, loadUnit);
for (const proto of protos) {
EggPrototypeFactory.instance.registerPrototype(proto, loadUnit);
}
}
}
}
}
42 changes: 40 additions & 2 deletions standalone/standalone/src/Runner.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { ModuleConfigUtil, ModuleReference, RuntimeConfig } from '@eggjs/tegg-common-util';
import {
EggPrototype,
EggPrototype, EggPrototypeLifecycleUtil,
LoadUnit,
LoadUnitFactory,
LoadUnitLifecycleUtil,
} from '@eggjs/tegg-metadata';
import {
ContextHandler,
EggContainerFactory, EggContext,
EggContainerFactory, EggContext, EggObjectLifecycleUtil,
LoadUnitInstance,
LoadUnitInstanceFactory,
ModuleLoadUnitInstance,
} from '@eggjs/tegg-runtime';
import { EggProtoImplClass, PrototypeUtil } from '@eggjs/tegg';
import { StandaloneUtil, MainRunner } from '@eggjs/tegg/standalone';
import { CrosscutAdviceFactory } from '@eggjs/tegg/aop';
import { EggObjectAopHook, EggPrototypeCrossCutHook, LoadUnitAopHook } from '@eggjs/tegg-aop-runtime';

import { EggModuleLoader } from './EggModuleLoader';
import { InnerObject, StandaloneLoadUnit, StandaloneLoadUnitType } from './StandaloneLoadUnit';
import { StandaloneContext } from './StandaloneContext';
import { StandaloneContextHandler } from './StandaloneContextHandler';
import { ModuleConfigHolder, ModuleConfigs } from './ModuleConfigs';
import { ConfigSourceQualifierAttribute } from './ConfigSource';
import { ConfigSourceLoadUnitHook } from './ConfigSourceLoadUnitHook';
import { LoadUnitInnerClassHook } from './LoadUnitInnerClassHook';

export interface RunnerOptions {
/**
Expand All @@ -40,6 +44,12 @@ export class Runner {
private runnerProto: EggPrototype;
private configSourceEggPrototypeHook: ConfigSourceLoadUnitHook;

private readonly loadUnitInnerClassHook: LoadUnitInnerClassHook;
private readonly crosscutAdviceFactory: CrosscutAdviceFactory;
private readonly loadUnitAopHook: LoadUnitAopHook;
private readonly eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;
private readonly eggObjectAopHook: EggObjectAopHook;

loadUnits: LoadUnit[] = [];
loadUnitInstances: LoadUnitInstance[] = [];
innerObjects: Record<string, InnerObject[]>;
Expand Down Expand Up @@ -96,6 +106,20 @@ export class Runner {
this.loadUnitLoader = new EggModuleLoader(this.moduleReferences);
const configSourceEggPrototypeHook = new ConfigSourceLoadUnitHook();
LoadUnitLifecycleUtil.registerLifecycle(configSourceEggPrototypeHook);

this.loadUnitInnerClassHook = new LoadUnitInnerClassHook();
LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitInnerClassHook);

// TODO refactor with egg module
// aop runtime
this.crosscutAdviceFactory = new CrosscutAdviceFactory();
this.loadUnitAopHook = new LoadUnitAopHook(this.crosscutAdviceFactory);
this.eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(this.crosscutAdviceFactory);
this.eggObjectAopHook = new EggObjectAopHook();

EggPrototypeLifecycleUtil.registerLifecycle(this.eggPrototypeCrossCutHook);
LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitAopHook);
EggObjectLifecycleUtil.registerLifecycle(this.eggObjectAopHook);
}

async init() {
Expand Down Expand Up @@ -165,5 +189,19 @@ export class Runner {
if (this.configSourceEggPrototypeHook) {
LoadUnitLifecycleUtil.deleteLifecycle(this.configSourceEggPrototypeHook);
}

if (this.loadUnitInnerClassHook) {
LoadUnitLifecycleUtil.deleteLifecycle(this.loadUnitInnerClassHook);
}

if (this.eggPrototypeCrossCutHook) {
EggPrototypeLifecycleUtil.deleteLifecycle(this.eggPrototypeCrossCutHook);
}
if (this.loadUnitAopHook) {
LoadUnitLifecycleUtil.deleteLifecycle(this.loadUnitAopHook);
}
if (this.eggObjectAopHook) {
EggObjectLifecycleUtil.deleteLifecycle(this.eggObjectAopHook);
}
}
}
182 changes: 182 additions & 0 deletions standalone/standalone/test/fixtures/aop-module/Hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import {
ContextProto,
Inject,
SingletonProto,
} from '@eggjs/tegg';
import {
Advice,
AdviceContext,
Crosscut,
IAdvice,
Pointcut,
PointcutType,
} from '@eggjs/tegg/aop';
import assert from 'assert';

export interface CallTraceMsg {
className: string;
methodName: string;
id: number;
name: string;
result?: string;
adviceParams?: any;
}

@SingletonProto()
export class CallTrace {
msgs: Array<CallTraceMsg> = [];

addMsg(msg: CallTraceMsg) {
this.msgs.push(msg);
}
}

export const pointcutAdviceParams = {
point: Math.random()
.toString(),
cut: Math.random()
.toString(),
};

@Advice()
export class PointcutAdvice implements IAdvice<Hello> {
@Inject()
callTrace: CallTrace;

async beforeCall(ctx: AdviceContext<Hello>): Promise<void> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
this.callTrace.addMsg({
className: PointcutAdvice.name,
methodName: 'beforeCall',
id: ctx.that.id,
name: ctx.args[0],
adviceParams: ctx.adviceParams,
});
}

async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
this.callTrace.addMsg({
className: PointcutAdvice.name,
methodName: 'afterReturn',
id: ctx.that.id,
name: ctx.args[0],
result,
adviceParams: ctx.adviceParams,
});
}

async afterThrow(ctx: AdviceContext<Hello, any>, error: Error): Promise<void> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
this.callTrace.addMsg({
className: PointcutAdvice.name,
methodName: 'afterThrow',
id: ctx.that.id,
name: ctx.args[0],
result: error.message,
adviceParams: ctx.adviceParams,
});
}

async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
this.callTrace.addMsg({
className: PointcutAdvice.name,
methodName: 'afterFinally',
id: ctx.that.id,
name: ctx.args[0],
adviceParams: ctx.adviceParams,
});
}

async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
ctx.args[0] = `withPointAroundParam(${ctx.args[0]})`;
const result = await next();
return `withPointAroundResult(${result}${JSON.stringify(pointcutAdviceParams)})`;
}
}

@ContextProto()
export class Hello {
id = 233;

@Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })
async hello(name: string) {
return `hello ${name}`;
}

@Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })
async helloWithException(name: string) {
throw new Error(`ops, exception for ${name}`);
}

}

export const crosscutAdviceParams = {
cross: Math.random()
.toString(),
cut: Math.random()
.toString(),
};

@Crosscut({
type: PointcutType.CLASS,
clazz: Hello,
methodName: 'hello',
}, { adviceParams: crosscutAdviceParams })
@Advice()
export class CrosscutAdvice implements IAdvice<Hello, String> {
@Inject()
callTrace: CallTrace;

async beforeCall(ctx: AdviceContext<Hello, {}>): Promise<void> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
this.callTrace.addMsg({
className: CrosscutAdvice.name,
methodName: 'beforeCall',
id: ctx.that.id,
name: ctx.args[0],
adviceParams: ctx.adviceParams,
});
}

async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
this.callTrace.addMsg({
className: CrosscutAdvice.name,
methodName: 'afterReturn',
id: ctx.that.id,
name: ctx.args[0],
result,
adviceParams: ctx.adviceParams,
});
}

async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
this.callTrace.addMsg({
className: CrosscutAdvice.name,
methodName: 'afterFinally',
id: ctx.that.id,
name: ctx.args[0],
adviceParams: ctx.adviceParams,
});
}

async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {
assert.ok(ctx.adviceParams);
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
ctx.args[0] = `withCrosscutAroundParam(${ctx.args[0]})`;
const result = await next();
return `withCrossAroundResult(${result}${JSON.stringify(ctx.adviceParams)})`;
}
}
15 changes: 15 additions & 0 deletions standalone/standalone/test/fixtures/aop-module/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ContextProto, Inject } from '@eggjs/tegg';
import { Runner, MainRunner } from '@eggjs/tegg/standalone';
import { Hello } from './Hello';


@Runner()
@ContextProto()
export class Foo implements MainRunner<string> {
@Inject()
hello: Hello;

async main(): Promise<string> {
return await this.hello.hello('aop');
}
}
6 changes: 6 additions & 0 deletions standalone/standalone/test/fixtures/aop-module/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "aop-module",
"eggModule": {
"name": "aopModule"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export abstract class AbstractContextHello {
abstract hello(): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export abstract class AbstractSingletonHello {
abstract hello(): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum ContextHelloType {
FOO = 'FOO',
BAR = 'BAR',
}

export enum SingletonHelloType {
FOO = 'FOO',
BAR = 'BAR',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ContextProto, Inject, EggObjectFactory } from '@eggjs/tegg';
import { ContextHelloType, SingletonHelloType } from './FooType';
import { AbstractContextHello } from './AbstractContextHello';
import { AbstractSingletonHello } from './AbstractSingletonHello';

@ContextProto()
export class HelloService {
@Inject()
private readonly eggObjectFactory: EggObjectFactory;

async hello(): Promise<string[]> {
const helloImpls = await Promise.all([
this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.FOO),
this.eggObjectFactory.getEggObject(AbstractContextHello, ContextHelloType.BAR),
this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.FOO),
this.eggObjectFactory.getEggObject(AbstractSingletonHello, SingletonHelloType.BAR),
]);
const msgs = helloImpls.map(helloImpl => helloImpl.hello());
return msgs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ContextHelloType } from '../FooType';
import { ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/tegg';
import { AbstractContextHello } from '../AbstractContextHello';

export const CONTEXT_HELLO_ATTRIBUTE = 'CONTEXT_HELLO_ATTRIBUTE';

export const ContextHello: ImplDecorator<AbstractContextHello, typeof ContextHelloType> =
QualifierImplDecoratorUtil.generatorDecorator(AbstractContextHello, CONTEXT_HELLO_ATTRIBUTE);

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SingletonHelloType } from '../FooType';
import { ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/tegg';
import { AbstractSingletonHello } from '../AbstractSingletonHello';

export const SINGLETON_HELLO_ATTRIBUTE = 'SINGLETON_HELLO_ATTRIBUTE';

export const SingletonHello: ImplDecorator<AbstractSingletonHello, typeof SingletonHelloType> =
QualifierImplDecoratorUtil.generatorDecorator(AbstractSingletonHello, SINGLETON_HELLO_ATTRIBUTE);
Loading
Loading