서버/Express

ExpressJS typedi

realtrynna 2022. 11. 1. 16:22

 

ExpressJS typedi

의존성 주입(Dependency Injection)은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이며 코드를 구조화하는데 사용되는 패턴이다. 생성자를 통해 클래스와 함수의 의존성을 주입하는 방식으로 모듈 안에서 클래스를 불러오거나 새로 생성하는 걸 피할 수 있으며 객체 지향적 아키텍처 설계를 통해 유지 보수성을 높일 수 있다.

 

의존성: 함수, 클래스 등이 내부에 다른 함수, 클래스를 사용

주입: 함수, 클래스 등이 내부에 사용하는 다른 함수, 클래스를 내부에서 생성하는 게 아닌 외부에서 생성하여 넣어줌

 

의존성

다음은 User 클래스가 Attack이라는 클래스에 의존하고 있는 코드다. 

class Attack {
    public normalAttack() {}
}

class User {
    private attack: Attack

    constructor() {
        this.attack = new Attack();
    }

    public normalAttack() {
        this.attack.normalAttack();
    }
}

const user: User = new User();

user.normalAttack();

 

문제

새로운 Move 클래스를 생성 후 Attack 클래스를 상속받는다. 이런 경우 User 클래스가 Attack 클래스에 의존하고 있어 클래스 데이터가 변경이 일어날 경우 직접 User 클래스를 수정해야 한다. 프로젝트의 규모가 커져 수십 개의 클래스를 직접 수정해야 한다고 하면 매우 비효율적이다.

class Attack {
    public normalAttack() {};
}

class Move extends Attack {}

class User {
    private attack: Attack;

    constructor() {
        this.attack = new Move();
    }

    public normalAttack() {
        this.attack.normalAttack();
    }
}

const user: User = new User();

user.normalAttack();

 

의존성 주입

의존하고 있는 클래스를 사용하고자 하는 클래스의 생성자로 받아온다. User 클래스를 호출하여 외부에 있는 Attack 클래스를 넣어준다. 이에 따라 의존성이 변경되어도 내부 클래스를 수정할 필요가 없어지며 의존성 사용을 결정하는 게 User 클래스가 되는데 이를 의존 관계 역전이라고 부른다.

class Attack {
    public normalAttack() {};
}

class User {
    private attack: Attack;

    constructor(attack: Attack) {
        this.attack = attack;
    }

    public normalAttack() {
        this.attack.normalAttack();
    }
}

const user: User = new User(new Attack);

user.normalAttack();

 

제어의 역전

User 클래스가 종속하고 있는 클래스들이 수십 개가 된다면 사용자가 직접 의존성의 인스턴스를 생성하여 주입하는 건 매우 비효율적이다. 이런 문제를 IoC 컨테이너(외부)에서 객체를 생성해 주입할 수 있는 typedi를 통해 해결할 수 있다. 사용자가 직접 인스턴스를 생성하고 제어권을 갖는 게 아니라 typedi가 제어권을 갖는데 이를 제어의 역전이라고 부른다.

 

typedi

노드 진영에서 사용가능한 의존성 주입을 위한 라이브러리다. 

 

사용

typedi와 reflect-metadata를 설치한다.

npm i typedi reflect-metadata

 

tsconfig.json 설정 파일에 다음 2가지 속성을 추가한다.

  "compilerOptions": {                               
    "experimentalDecorators": true,                   
    "emitDecoratorMetadata": true
 }

 

프로젝트 메인 파일(app) 최상단에 다음 코드를 추가한다.

import 'reflect-metadata';

 

클래스를 사용하는 쪽은 typedi에 Container와 Controller 클래스를 임포트 하여 Container.get 메서드로 Controller에 클래스를 가져오고 인스턴스를 생성할 쪽은 Service를 임포트 후 클래스 상단에 @Service 데코레이터를 붙여준다. typedi를 사용하면 사용할 클래스를 가져온 후 다시 인스턴스를 생성하는 과정을 생략할 수 있다.

// Router
import { Router } from "express";
import { Container } from "typedi";

import { UserController } from "../controllers/_.exporter";
import { asyncHandler } from "../utils/_.exporter";

const userRouter = Router();

const userController = Container.get(UserController);

userRouter.post("/create", userController.createUser);

export { userRouter };
// Controller
import { Request, Response } from "express";
import { Service } from "typedi";

import { UserService } from "../services/_.exporter";
import { IUserController } from "../types/_.exporter";
import { CreateUserDto } from "../dtos/_.exporter";
import { validatorDto } from "../utils/_.exporter";

@Service()
export class UserController implements IUserController{
    constructor(private readonly userService: UserService) {}

    createUser = async (
        { body }: Request<unknown, unknown, CreateUserDto>,
        res: Response
    ) => {
        await validatorDto(new CreateUserDto(body));
        
        const result = await this.userService.createUser(body);

        return res.json(result);
    };
}

 

참고 자료

https://www.npmjs.com/package/typedi

https://docs.nestjs.com/providers#custom-providers

https://ko.wikipedia.org/wiki/%EC%9D%98%EC%A1%B4%EC%84%B1_%EC%A3%BC%EC%9E%85

'서버 > Express' 카테고리의 다른 글

ExpressJS Google Spreadsheet API  (0) 2023.01.06
ExpressJS sharp  (0) 2022.10.08
ExpressJS Jest ES6 import export  (0) 2022.08.10
ExpressJS express-validator 유효성 검사  (0) 2022.06.23
ExpressJS nodemailer 비밀번호 초기화  (0) 2022.06.21