Computer-Programming/Node

NestJS에 적용된 디자인 패턴 - Singleton, Factory, Strategy, Decorator

sila_kr 2024. 4. 2. 11:49
반응형

Framework와 Design Pattern

프레임워크와 디자인패턴

 

프레임워크와 디자인패턴은 프로그래머로 하여금 소프트웨어 개발을 할 때 어떤 방식으로 접근을 하고 코드를 작성 해야 되는가에 대해 모범 답안을 제시한다.

 

디자인 패턴은 소프트웨어에서 필요 할 수 있는 시나리오들을 상정한 상태에서 어떻게 구현을 하면 좋은지에 대해서 알려준다.

 

프레임워크의 경우에는  시나리오들을 상정한 상태에서 소프트웨어 개발자로 하여금 정해진 규칙에 따라 구현을 하도록 강제함으로써 좋은 소프트웨어 품질을 유지 할 수 있도록 해준다.

 

결국에 목적은 비슷하기도하고 프레임워크 자체가 소프트웨어로 개발이 되어 있기 때문에 프레임워크 조차도 디자인 패턴이 적용되어 개발이 되어 있다.

 

때문에 Node.js 기반으로 만들어진 프레임워크 Nest.js에서 사용하고 있는 디자인 패턴을 알아 보려고 한다. 

 

 


Singleton

싱글턴 패턴

(Singleton pattern)을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글턴 패턴이라고 한다. 주로 공통된 객체를 여러개 생성해서 사용하는 DBCP(DataBase Connection Pool)와 같은 상황에서 많이 사용된다.

- 위키백과

 

싱글톤은 인스턴스를 리소스를 공유하고 관리하는 매우 간단하면서도 강력한 방법이다. 인스턴스를 하나만 생성을 해서 공유하여 사용하는 방식으로 메모리를 효율적으로 사용하고 인스턴스 접근에 대해 제어함으로써 동시성 제어도 효과적으로 관리 할 수 있다.

 

NestJS에서는 @Injectable() 데코레이터가 Singleton이 적용이 되어 있다. 이로 인해서 DI(Dependency Injection)시에 

서비스는 인스턴스를 하나만 생성을 하고 작업을 완료하면 인스턴스를 해제하지 않고 다른 요청을 처리 할 때 재사용한다.

 

여기서 발생하는 문제점은 인스턴스가 하나이기 때문에 요청에 대해서 데드락이 발생하면 같은 서비스에 대해 처리 할 때 완료하기 전까지 기다려야 되는 경우가 발생 할 수 있다. 이를 방지하기 위해서는 데드락이 발생 할 수 있는 요청에 대해서는 방지하는 코드를 작성하는 것이 중요하다.

 

import { Injectable, TimeoutException } from '@nestjs/common';

@Injectable()
export class SomeService {
  async fetchDataWithTimeout(url: string, timeout: number) {
    // 데이터를 가져오는 프로미스
    const fetchDataPromise = this.fetchData(url);

    // 타임아웃을 구현하는 프로미스
    const timeoutPromise = new Promise((resolve, reject) => {
      setTimeout(() => reject(new TimeoutException('Request timed out')), timeout);
    });

    // Promise.race를 사용하여 두 프로미스 중 하나가 먼저 완료되면 그 결과를 반환
    return Promise.race([fetchDataPromise, timeoutPromise]);
  }

  async fetchData(url: string) {
    // 여기에 데이터를 가져오는 로직 구현
    // 예를 들어, HTTP 요청을 보내고 응답을 반환
  }
}

 

구현하는 방법이야 여러가지 있을 수 있겠지만 promise.race()를 사용하여 정해진 시간 동안 동작하는 타임아웃 함수와 요청을 처리하는 함수 중에 먼저 끝나는 함수가 있다면 나머지 함수들은 강제적으로 실행이 종료 된다. 이러한 동작을 통해서 인스턴스를 점유해서 발생하는 대기 현상을 방지 할 수 있다.


Factory

 

팩토리 메서드 패턴

(Factory method pattern)은 객체지향 디자인 패턴이다. Factory method는 부모(상위) 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴이며. 자식(하위) 클래스가 어떤 객체를 생성할지를 결정하도록 하는 패턴이기도 하다. 부모(상위) 클래스 코드에 구체 클래스 이름을 감추기 위한 방법으로도 사용한다.

- 위키백과

 

팩토리 매세드 패턴은 클래스 생성자를 생성하는 쪽이 알지 못하게 감춤으로써 클래스 생성 코드를 작성하는 입장에서 프로그램의 설정이나 조건에 대한 고려를 줄이고 코드를 간결하게 표현 할 수 있게 하는 패턴이다.

 

import { Module } from '@nestjs/common';

@Module({
  providers: [
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async () => {
        const type = process.env.DATABASE_TYPE;
        let connection;

        if (type === 'mysql') {
          connection = await createMySqlConnection();
        } else if (type === 'postgres') {
          connection = await createPostgresConnection();
        }

        return connection;
      },
    },
  ],
})
export class DatabaseModule {}

 

위의 예시와 같이 NestJS에서는 Module의 provides를 사용할 때 useFactory에 해당 생성자를 지정하거나 구현함으로써 적용되게 되어있다.

 

async function createMySqlConnection() {
  // MySQL 커넥션 생성 로직
}

async function createPostgresConnection() {
  // PostgreSQL 커넥션 생성 로직
}

 

위와 같이 클래스 생성자의 구체 생성 함수를 작성한다.

 

import { Inject, Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(@Inject('DATABASE_CONNECTION') private dbConnection) {}

  async findAll() {
    // 데이터베이스 커넥션 사용
  }
}

 

Service에서는 생성자 클래스의 구현에 신경쓰지 않고도 호출하여 데이터베이스 커넥션을 동작 할 수 있다. 

반응형

Strategy

전략 패턴(strategy pattern)

전략 패턴(strategy pattern) 또는 정책 패턴(policy pattern)은 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다. 전략 패턴은

특정한 계열의 알고리즘들을 정의하고
각 알고리즘을 캡슐화하며
이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.

전략은 알고리즘을 사용하는 클라이언트와는 독립적으로 다양하게 만든다. 전략은 유연하고 재사용 가능한 객체 지향 소프트웨어를 어떻게 설계하는지 기술하기 위해 디자인 패턴의 개념을 보급시킨 디자인 패턴(Gamma 등)이라는 영향력 있는 책에 포함된 패턴들 가운데 하나이다.

- 위키백과

 

전략 패턴은 소프트웨어를 구현하다보면 여러가지 알고리즘이 적용 되어야 하고 이런 알고리즘들을 간결하게 구현하면서도 캡슐화를 통해서 결합도 낮은 소프트웨어를 개발하기 위한 방법이다. strategy 패턴의 구현 예시는 다음과 같다.

 

Local Strategy

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({ usernameField: 'email' });
  }

  async validate(email: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(email, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

 

Jwt Strategy

import { Strategy, ExtractJwt } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
  constructor(private authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'yourSecretKey',
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

 

@nestjs/passport를 이용하여 각각 local에 정의된 인증 방식을 사용할 것인지, jwt인증 방식을 사용 할 것 인지를 선택하여 동작을 할 수 있는 로직이다.

 

import { Controller, Post, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {
  @Post('login')
  @UseGuards(AuthGuard('local'))
  async login(@Request() req) {
    return req.user;
  }
  
  @Get('profile')
  @UseGuards(AuthGuard('jwt'))
  getProfile(@Request() req) {
    return req.user;
}

 

AuthGuard의 string 매개 변수에 따라 전략이 선택 되고 각각의 strategy에 정의 되어 있는 validate가 실행 된다. auth api를 호출하면 local 방식이고 profile을 호출 하면 jwt 방식으로 실행이 된다.

 


Decorator

데코레이터 패턴

데코레이터 패턴(Decorator pattern)이란 주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴으로, 기능 확장이 필요할 때 서브클래싱 대신 쓸 수 있는 유연한 대안이 될 수 있다.

-위키백과

 

데코레이터 패턴은 여러 프레임워크에서 구현이 되어 있는 매우, 매우 많이 사용하는 패턴이다. 확장성이 뛰어난 개발을 할 수 있게 때문에 자주 쓰인다. 

 

@Contoroller , @Get , @Post , @UseGuard, @UseInterceptors 등 이미 많은 기능들이 NestJS 상에서 구현이 되어 있으며 Custom Decorator를 구현 하여 사용 할 수 있기 때문에 항상 사용을 하고 있다고 할 수 있다.

 

https://docs.nestjs.com/custom-decorators

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

 

Custom Decorator의 경우 공식 홈페이지에 사용 예시가 자세하게 설명이 되어 있다.


 

 

반응형