Introduction
VonaJS AOP programming includes three capabilities:
Controller Aspect
: Applies logic to controller methodsInternal Aspect
: Applies logic to any method of any class within a classExternal Aspect
: Applies logic to any method of any class from the outside without changing the class source code
Controller Aspect
Controller Aspect List
- Middleware
- Guard
- Interceptor
- Pipe
- Filter
Execution Sequence Diagram
The execution sequence diagram for controller aspects is as follows:
Onion Model
:Middleware
andInterceptor
support theOnion Model
, allowing aspect logic to be executed before and afterController Action
Middleware
: Three types of Middleware are provided for different execution sequence stages:Middleware System
,Middleware Global
, andMiddleware Local
, allowing for more refined aspect logicRoute Match
: Only theMiddleware System
is executed before route matching; the rest are executed afterFilter
: If an exception is thrown at any stage,Filter
will be executed to customize the processing logic oferror information
anderror log
Internal Aspect
Internal aspects provide two mechanisms: AOP Method
and Magic Method
1. AOP Method
Implement aspect logic directly on Class Method through decorator
Example: Database Transaction
diff
class ServiceStudent {
+ @Database.transaction()
async update(id: TableIdentity, student: DtoStudentUpdate) {
return await this.scope.model.student.update({
...student,
id,
});
}
}
@Database.transaction
: A decorator implemented through theAOP Method
mechanism that can directly provide database transaction capabilities
Example: Logging
diff
class ServiceStudent {
+ @Log()
async update(id: TableIdentity, student: DtoStudentUpdate) {
return await this.scope.model.student.update({
...student,
id,
});
}
}
@Log
: A decorator implemented using theAOP Method
mechanism directly provides logging capabilities
2. Magic Method
Dynamic properties or methods can be implemented through __get__
and __set__
within the Class
Example: Obtaining a model instance
diff
class ServiceStudent {
async update(id: TableIdentity, student: DtoStudentUpdate) {
+ return await this.scope.model.student.update({
...student,
id,
});
}
}
this.scope.model.xxx
: Instead of usingdependency injection
,dependency lookup
is used to obtain the model instance directly from the scope object, simplifying the code writing style
Implementation Idea
The system provides a ServiceModelResolver
class for dynamic model instance resolution. The code is as follows:
typescript
class ServiceModelResolver {
protected __get__(prop: string) {
const beanFullName = `${this[SymbolModuleScope]}.model.${prop}`;
return this.bean._getBean(beanFullName as any);
}
}
- When
this.scope.model.student
is called, the__get__
method is automatically executed, passing theprop: 'student'
parameter - The
prop
parameter is combined with the current module name to formbeanFullName
- The model instance is retrieved from the global container using
beanFullName
and returned to the caller
External Aspect
Using the update
method of the ServiceStudent
class as an example, we implement logging capabilities through the external aspect
:
typescript
import { Aop } from 'vona-module-a-aspect';
@Aop({ match: 'demo-student.service.student' })
class AopLog {
async update(_args: Parameters<any>, next: Function, _receiver: any) {
const timeBegin = Date.now();
const res = await next();
const timeEnd = Date.now();
console.log('time: ', timeEnd - timeBegin);
return res;
}
}
@Aop
: This decorator is used to implement theexternal aspect
match
: This option is used to associate theAopLog
class with theServiceStudent
class, which beanFullName isdemo-student.service.student
update
: Provides theupdate
method with the same name asServiceStudent
inAopLog
to implement custom logic