의존성 주입 (23/01/10)

nazzzo·2023년 1월 10일
0

1. 의존성 주입



지금까지 MVC 패턴을 토대로 백엔드 서버의 구조를 살펴보면
라우터는 컨트롤러를, 컨트롤러는 서비스를, 서비스는 레포지토리를 요청합니다

이러한 구조로 짜여진 코드를 의존 관계에 놓여있다고 말하는데요
코드의 흐름을 한 눈에 파악하기 편한 반면
어느 모듈 하나에만 문제가 생겨도 모든 코드를 사용할 수 없게 된다는 문제점이 있습니다


의존성 주입(Dependency Injection)은 프로그래밍 기법 중 하나로,
코드의 결합도를 줄이고 의존 관계를 외부에서 관리하는 테크닉을 말합니다

의존성 주입을 사용하려면 모든 모듈에 대해 의존성 주입이 필요하다는(빌드시간 증가) 단점이 있지만,
대신 다음과 같은 장점을 지니게 됩니다

  • 코드의 수정이 간편해집니다 (유지보수성 ↑)
    특히 특정 요소가 바뀌더라도 이를 사용하는 모든 곳을 같이 수정하지 않아도 됩니다
  • 모듈화: 모듈 각각의 의존성을 끊은 만큼 각 모듈은 독립적으로 개발 및 사용이 가능해집니다

오늘은 의존성 주입의 여러가지 방법론 중에서, 두 가지 방법을 예제 코드를 통해 살펴보기로 하겠습니다


2. 고차함수를 이용하는 방법

repository

module.exports = (mysql) => {
  return {
    findAll: async () => {
      const result = await mysql.query(`SELECT * from Comments`);
      return result;
    },
  };
};

Service

module.exports = (Repository) => {
    return {
        list: async ()=>{
            const list = await Repository.findAll()
            return list
        }
    }
}

modules (의존성 주입자)

const mysql = require("../models/index");
const Repository = require("./comment.repository")(mysql);
const Service = require("./comment.service")(Repository);
const Controller = require("./comment.controller")(Service);

// Repository.findAll().then((v) => console.log(v));
// Service.list().then((v) => console.log(v));

각각의 의존성을 modules로 옮겨오고 함수의 인자를 통해 의존성을 주입합니다
하지만 이 방법에는 코드의 뎁스 관리가 어렵다는 문제가 있습니다



3. Class를 이용하는 방법


repository

class CommentRepository {
    constructor ({ mysql }) {
        this.mysql = mysql
    }
    
    async findAll(){
        try {
            const [list] = await this.mysql.query(`SELECT * From Comments`)
            // mysql에 접근하기 위한 this
            return list
        } catch (e) {
            throw new Error(e)
        }
    }
}

module.exports = CommentRepository

service

class CommentService {
  constructor({ commentRepository }) {
    this.commentRepository = commentRepository;
  }

  async list () {
    try {
        const list = await this.commentRepository.findAll()
        if(list.length === 0) throw new Error ("내용이 없습니다")
        return list
    } catch (e) {
        throw new Error(e)
    }
  }
}

module.exports = CommentService;

controller

*컨트롤러의 구조는 약간 다릅니다
(req, res를 인자로 받고 에러처리는 미들웨어에 전달)

class CommentController {
    constructor({ commentService }) {
        this.commentService = commentService;
    }
    async getList (req, res, next) {
        try {
            const comments = await this.commentService.list()
            res.json(comments)
        } catch (e) {
            next(e);
        }

    }
}

module.exports = CommentController

modules

const mysql = require('../models/index')
const CommentRepository = require("./comment.repository");
const CommentService = require("./comment.service");
const CommentController = require("./comment.controller");
const config = require("../config");

const repository = new CommentRepository({ Comments });
const service = new CommentService({ commentRepository: repository, config });
const controller = new CommentController({ commentService: service });

// repository.findAll().then(v=>console.log(v))
// service.list().then(v=>console.log(v))

module.exports = {
  repository,
  service,
  controller,
};

Router

const express = require("express");
const router = express.Router();
const { controller } = require("./comment.module");

router.get("/", (req, res, next) => controller.getList(req, res, next));
router.post("/", (req, res, next) => controller.postComment(req, res, next));
router.put("/:id", (req, res, next) => controller.putComment(req, res, next));
router.delete("/:id", (req, res, next) => controller.deleteComment(req, res, next));

module.exports = router;

class를 이용하는 방법이 약간 더 번거롭습니다
대신 뎁스가 줄어서 try & catch문을 끼워넣는데도 무리가 없고,
객체 속성을 외부에서 추가하는 등의 커스터마이징도 가능합니다



3-1. this 바인딩 방지하기


컨트롤러와 라우터 코드를 다시 한 번 살펴보겠습니다


Controller

class CommentController {
    constructor({ commentService }) {
        this.commentService = commentService;
    }
    async getList (req, res, next) {
        try {
            const comments = await this.commentService.list()
            res.json(comments)
        } catch (e) {
            next(e);
        }

    }
}

Router

// 화살표 함수의 의미는?
router.get("/", (req, res, next) => controller.getList(req, res, next));

this가 가리키는 대상은 함수를 호출할 때 결정됩니다

즉 라우터에서 컨트롤러 코드를 실행하면 함수의 실행주체가 라우터가 돠면서 this가 가리키는 대상이 바뀝니다
(getList 함수가 정의된 CommentController 클래스의 인스턴스가 아니라
router 객체의 this 값을 참조하게 됩니다)

이러한 this 바인딩 현상을 막고 컨트롤러 코드를 실행하려면
함수가 정의된 스코프(controller의 getList 함수)에서부터 this를 상속받아야 합니다
메서드를 사용해서 해결하는 방법도 있지만 화살표 함수를 사용하는 편이 보다 더 간단하게 처리할 수 있습니다
위와 같이 화살표 함수를 사용하면 (함수가 정의되었던) 상위 스코프의 this 값을 상속받게 됩니다

0개의 댓글