지금까지 MVC 패턴을 토대로 백엔드 서버의 구조를 살펴보면
라우터는 컨트롤러를, 컨트롤러는 서비스를, 서비스는 레포지토리를 요청합니다
이러한 구조로 짜여진 코드를 의존 관계에 놓여있다고 말하는데요
코드의 흐름을 한 눈에 파악하기 편한 반면
어느 모듈 하나에만 문제가 생겨도 모든 코드를 사용할 수 없게 된다는 문제점이 있습니다
의존성 주입(Dependency Injection)은 프로그래밍 기법 중 하나로,
코드의 결합도를 줄이고 의존 관계를 외부에서 관리하는 테크닉을 말합니다
의존성 주입을 사용하려면 모든 모듈에 대해 의존성 주입이 필요하다는(빌드시간 증가) 단점이 있지만,
대신 다음과 같은 장점을 지니게 됩니다
오늘은 의존성 주입의 여러가지 방법론 중에서, 두 가지 방법을 예제 코드를 통해 살펴보기로 하겠습니다
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로 옮겨오고 함수의 인자를 통해 의존성을 주입합니다
하지만 이 방법에는 코드의 뎁스 관리가 어렵다는 문제가 있습니다
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문을 끼워넣는데도 무리가 없고,
객체 속성을 외부에서 추가하는 등의 커스터마이징도 가능합니다
컨트롤러와 라우터 코드를 다시 한 번 살펴보겠습니다
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 값을 상속받게 됩니다