-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[Feat] AOP decorator 추가
- Loading branch information
Showing
9 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
// IntelliSense를 사용하여 가능한 특성에 대해 알아보세요. | ||
// 기존 특성에 대한 설명을 보려면 가리킵니다. | ||
// 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요. | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"type": "node", | ||
"request": "launch", | ||
"name": "개발 디버그", | ||
"skipFiles": ["<node_internals>/**"], | ||
"runtimeExecutable": "npm", | ||
"runtimeArgs": ["run", "start:dev"], | ||
"autoAttachChildProcesses": true, | ||
"restart": true, | ||
"sourceMaps": true, | ||
"cwd": "${workspaceRoot}", | ||
"console": "integratedTerminal", | ||
"protocol": "inspector" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { DiscoveryModule } from '@nestjs/core'; | ||
import { AutoAspectExecutor } from './auto-aspect.executor'; | ||
import { TransactionDecorator } from './transactional.decorator'; | ||
|
||
@Module({ | ||
imports: [DiscoveryModule], | ||
providers: [AutoAspectExecutor, TransactionDecorator], | ||
}) | ||
export class AopModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Injectable, SetMetadata, applyDecorators } from '@nestjs/common'; | ||
|
||
export const ASPECT = Symbol('ASPECT'); | ||
|
||
export const Aspect = (metadataKey: string | symbol) => | ||
applyDecorators(SetMetadata(ASPECT, metadataKey), Injectable); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { Injectable, OnModuleInit } from '@nestjs/common'; | ||
import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core'; | ||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; | ||
import { ASPECT } from './aspect'; | ||
|
||
/** | ||
* 모듈 초기화 시 전체 프로바이더를 탐색하여 AOP를 활용하는 메서드를 찾습니다. | ||
*/ | ||
@Injectable() | ||
export class AutoAspectExecutor implements OnModuleInit { | ||
constructor( | ||
private readonly discoveryService: DiscoveryService, | ||
private readonly metadataScanner: MetadataScanner, | ||
private readonly reflector: Reflector, | ||
) {} | ||
|
||
onModuleInit() { | ||
const providers = this.discoveryService.getProviders(); | ||
|
||
const lazyDecorators = this.lookupLazyDecorator(providers); | ||
if (lazyDecorators.length === 0) { | ||
return; | ||
} | ||
|
||
providers | ||
.filter((wrapper) => wrapper.isDependencyTreeStatic()) | ||
.filter(({ instance }) => instance && Object.getPrototypeOf(instance)) | ||
.forEach(({ instance }) => { | ||
this.metadataScanner.scanFromPrototype( | ||
instance, | ||
Object.getPrototypeOf(instance), | ||
(methodName) => { | ||
lazyDecorators.forEach((lazyDecorator) => { | ||
const metadataKey = this.reflector.get( | ||
ASPECT, | ||
lazyDecorator.constructor, | ||
); | ||
|
||
const metadata = this.reflector.get( | ||
metadataKey, | ||
instance[methodName], | ||
); | ||
if (!metadata) { | ||
return; | ||
} | ||
const wrappedMethod = lazyDecorator.wrap( | ||
instance, | ||
instance[methodName], | ||
metadata, | ||
); | ||
instance[methodName] = wrappedMethod; | ||
}); | ||
}, | ||
); | ||
}); | ||
} | ||
|
||
private lookupLazyDecorator(providers: InstanceWrapper<any>[]) { | ||
return providers | ||
.filter((wrapper) => wrapper.isDependencyTreeStatic()) | ||
.filter(({ instance, metatype }) => { | ||
if (!instance || !metatype) { | ||
return false; | ||
} | ||
|
||
const aspect = this.reflector.get<string>(ASPECT, metatype); | ||
if (!aspect) { | ||
return false; | ||
} | ||
|
||
return instance.wrap; | ||
}) | ||
.map(({ instance }) => instance); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type Decorator = (...args: any) => void | Promise<void>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Decorator } from './decorator.interface'; | ||
|
||
/** | ||
* 데코레이터의 초기화를 모듈이 생성되는 시점까지 늦춥니다. | ||
*/ | ||
export interface LazyDecorator { | ||
wrap(target: unknown, originalFn: any, options: void): Decorator | undefined; // TODO 반환 형식 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { LazyDecorator } from './lazy-decorator'; | ||
import { DataSource } from 'typeorm'; | ||
import { Aspect } from './aspect'; | ||
import { TRANSACTIONAL } from './transactional'; | ||
|
||
/** | ||
* TODO Transaction 전파 등 다양한 케이스를 고려해야 함. | ||
* 1. 해당 Decorator는 단순 명령을 처리하는 객체이므로, 내부 구현만 달라지면 OK | ||
* 2. Repository에서 트랜잭션을 매니징할 객체를 시그니처에 명시한다. -> 매니징 객체의 타입이 상당히 애매하다. 어댑터로 추상화를 도울 수 있도록 개선. | ||
*/ | ||
@Aspect(TRANSACTIONAL) | ||
export class TransactionDecorator implements LazyDecorator { | ||
constructor(private readonly dataSource: DataSource) {} // TypeORM에 의존하고 있으므로 주의 | ||
|
||
wrap(target: unknown, originalFn: any, _: void) { | ||
return async (...args: any[]) => { | ||
const queryRunner = this.dataSource.createQueryRunner(); | ||
await queryRunner.connect(); | ||
await queryRunner.startTransaction(); | ||
try { | ||
await originalFn.call(target, ...args, queryRunner.manager); | ||
await queryRunner.commitTransaction(); | ||
} catch (e) { | ||
await queryRunner.rollbackTransaction(); | ||
throw e; | ||
} finally { | ||
await queryRunner.release(); | ||
} | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { SetMetadata } from '@nestjs/common'; | ||
|
||
export const TRANSACTIONAL = Symbol('TRANSACTIONAL'); | ||
|
||
export const Transactional = () => SetMetadata(TRANSACTIONAL, true); |