
최근 React + Nestjs 를 이용한 웹 앱 프로젝트에서 서버를 담당하면서 구조에 관해 팀원들과 꽤 오랜 시간 의견을 나누었다
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getInfo(): string {
return this.appService.getInfo();
}
}
흔히 알려진 컨트롤러의 개념과 동일하다.
서비스 요청이 올 때의 입구, 진입점으로 요청을 받아 서비스 로직에 매핑시켜준다.
즉, 비즈니스 로직을 분리하기 위해 입구를 따로 둔 것이다.
프로바이더는 Nest의 기본 개념으로, Service, Repository등이 프로바이더로 취급된다.
@Injectable 데코레이터를 통해 의존성을 주입한다.
우리 백엔드 팀은 모두 Nest를 처음 접했다.
프로젝트의 기본적인 구조를 찾아봤을 때, 가장 보편적으로 사용하는.. 흔한 국룰 구조를 적용했다.
피쳐 별로 모듈을 구성하고, 모듈내에 provider, controller를 구성했다.
API를 작성하다가 생긴 문제는, A 모듈에서, B 모듈의 서비스 로직이 필요한 경우가 많아진다는 것이었다.
- 비즈니스 로직을 담은 Serivce를 또 다른 Service에서 import하여 사용할 것인가?
- 컨트롤러에서 import 하여 사용할 것인가?
각자 공식 독스부터 시작해서, Nest 오픈 소스 프로젝트 등을 서치해보았지만, 정해진 답이 없다고 느꼈고, 결국 우리 서비스의 경우에는 컨트롤러에서까지 로직이 생길 정도의 복잡한 로직은 없으니,, 컨트롤러에서 주입받자고 결정했다.
결론적으로,, 결국 이 구조는 다시 리팩토링 당했다(?)
Database CRUD 작업을 진행하면서, transaction을 적용해야 했고, 결국 컨트롤러에 로직이 길어지게 된 것이다.
트랜잭션을 적용했을 때..
await queryRunner.connect();
await queryRunner.startTransaction();
try {
...
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
개발을 하면서 느끼는 것은, 이렇게 뚜렷하게 정해진 답이 없는 이슈들이 많다는 것이다.
코드가 아니더라도, 이렇게 프로젝트의 구조만 해도 고민해야 하는 것이 많다.
중요한 것은 이 과정에서 우리 서비스의 구조, 규모, 로직 등에 대한 파악을 바탕으로 무엇이 어떤 이유로 더, 덜 적합한지를 깊은 논의를 통해 알아내고, 결정을 내려야 한다는 것이다.
단순히 서비스 로직이 아닌, 프로젝트의 구조에 대해 팀원들과 서치하고, 의견을 나누는 과정에서 많은 깨달음을 얻은 것 같다.
그날 작업은 못하고 회의에 리팩토링만 진행했지만....ㅎ
모두가 한층 더 성장한 것 같다!