Liner/ NestJS로 리팩토링

flobeeee·2021년 6월 3일
1

프로젝트

목록 보기
6/7
post-thumbnail

깃레포

🐳 1. sequelizeORM 사용

How to setup Sequelize migration in a NestJS Project 참고했다.

기존 프로젝트 셋팅에서 추가 or 수정된 부분

마이그레이션 명령어로 예제 테이블 생성까지 확인

🐳 2. 데이터구조 재설계

1차로 진행했던 스키마는 테마변경을 할 때, 해당 색깔을 찾아서 다 바꿔줘야하는 번거로움이 있었다.
그래서 색깔이 아닌 fk 만 바꿀수 있게 다시 설계하였다.
그리고 기존에 조인테이블을 user_page 로 만들어서
highlightId 로 입출력값을 다룰 때 헷갈렸어서 테이블 이름도 변경하였다.

API 문서도 다시 작성했다.

🐳 3. 관계설정

기존에는 model/index.js 에 아래처럼 설정했었다.

const { Theme, User, User_Page, Page } = sequelize.models;

Theme.hasMany(User);
User.belongsTo(Theme);

User_Page.belongsTo(User);
User.hasMany(User_Page);

User_Page.belongsTo(Page);
Page.hasMany(User_Page);

nestjs + typescript + sequelize 는 달랐다.
src/highlight/highlight.model.ts 에는 아래처럼 작성하였다.

import {
  Column,
  Model,
  Table,
  ForeignKey,
  BelongsTo,
} from 'sequelize-typescript';
import { Page } from 'src/page/page.model';
import { Theme } from 'src/theme/theme.model';
import { User } from 'src/user/user.model';

@Table
export class Highlight extends Model {
  @ForeignKey(() => User)
  userId: number;

  @ForeignKey(() => Page)
  pageId: number;

  @Column
  text: string;

  @BelongsTo(() => Theme)
  themeData: number;

  @ForeignKey(() => Theme)
  colorId: number;
}
  • @ForeignKey 만 있으면 관계설정이 되는 줄 알았다.
  • @BelongsTo 처럼 한번더 설정해줘야 관계가 정립이 된다.
    (테이블 조인해서 값을 가져오다가 안되서 알았다.)

🐳 4. app.module

이 파일에서는 누가 어떤 역할을 하는지 분명하게 알 수 있다.

@Module({
  imports: [
    SequelizeModule.forRoot({
      dialect: 'mysql',
      host: 'localhost',
      username: 'root',
      password: null,
      database: 'liner_nest',
      models: [User, Theme, Page, Highlight],
    }),
    UserModule,
    ThemeModule,
    PageModule,
    HighlightModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})

import {} from '' 부분이 너무 많아서 생략했다.
아무튼 imports 부분을 통해 requelize 를 연결했다.
controllers 에서는 express 의 라우팅 역할을 하고
providers 에서는 실제 서비스로직이 있다.

🐳 5. app.controller

리팩토링 전에는 전부 body 로 데이터를 받았다.
이번에는 body 는 물론 query, param 모두 사용해봤다.

기존에는 내용마다 각각 파일을 만들어서 조금 복잡하다고 생각했다.
이번에는 app.controller.ts 파일에 모든 라우팅을 정리했다.

@로 라우팅이 나눠지는데 한 눈에 보여서 직관적이라고 생각했다.

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  sayHi() {
    return this.appService.getHello();
  }

  @Post()
  createHighlight(
    @Body() createHighlightDto: CreateHighlightDto,
  ): Promise<resData> {
    return this.appService.create(createHighlightDto);
  }

  @Patch()
  updateHighlight(
    @Body() updateHighlightDto: UpdateHighlightDto,
  ): Promise<resData> {
    return this.appService.update(updateHighlightDto);
  }

  // http://localhost:3000/highlights?userId=1&pageUrl=naver.com&pageId=123 이면 123 우선
  @Get('highlights')
  readHighlights(
    @Query('userId') userId: number,
    @Query('pageUrl') pageUrl?: string,
    @Query('pageId') pageId?: number,
  ) {
    return this.appService.readhighlights(userId, pageId, pageUrl);
  }

  @Get(':userId')
  readAll(@Param('userId') userId: number) {
    return this.appService.readAll(userId);
  }

  @Delete(':userId/:highlightId')
  deleteHighlight(
    @Param('userId') userId: number,
    @Param('highlightId') highlightId: number,
  ) {
    return this.appService.delete(userId, highlightId);
  }

  @Put(':userId/:themeId')
  changeTheme(
    @Param('userId') userId: number,
    @Param('themeId') themeId: number,
  ) {
    return this.appService.changeTheme(userId, themeId);
  }
}

🐳 6. app.service

내가 제일 시간을 많이 쏟은 파일이다. (당연)
이번에는 서비스 관련 로직을 하나의 파일에 다 넣어보았다.
총 333줄의 코드가 나왔지만, 각각의 기능은 길지 않았다.

예전에는 기능마다 파일만들어서 쪼갰는데, 굉장히 귀찮았다.
내용이 적으니까 가능한 것 같다.
아마 실무를 하게 될 때는 쪼개는 게 맞다고 생각한다.

interface resData {
  highlightId: number;
  userId: number;
  pageId: number;
  colorHex: string;
  text: string;
}

이렇게 결과 인터페이스도 만들어 보았다.

각 기능의 로직에 관해 코드리뷰 받고싶다 ㅠ
더 나은 방법이 있는지도 알고싶고, 스키마는 적절한지도 평가받고 싶다.

이번에는 예외처리가 되는 부분이 없게 심혈을 기울였다.

const { userId, pageUrl, colorHex, text } = data;
    // 유저확인
    const checkUser = await User.findOne({
      where: { id: userId },
    });
    if (!checkUser) {
      throw new NotFoundException(`User with id: ${userId} not found`);
    }

    // Url 확인
    const checkUrl = await Page.findOrCreate({
      where: { pageUrl: pageUrl },
    });
    const pageId = checkUrl[0].getDataValue('id');

이렇게 파라미터에 대해서 하나하나 확인하는 과정은 물론
필수 파라미터가 들어오지 않는 부분에 대해서도 처리를 해줬다.

이렇게 세세하게 처리를 하니, 테스트에서 정말 머리가 지끈거렸다.

🐳 7. 테스트

각잡고 대부분의 경우의 수 따져가면서 코드 구현했더니.
테스트 진행할 때, 진짜 토나올뻔 했다.
그런데 이렇게 하는 게 맞는 거겠지?

예전 프로젝트 진행할 때는 테스트코드도 짜지않고
에러처리도 세세하게 진행하지 않았다.
그 당시 내 코드가 얼마나 부족했는지 이번 리팩토링을 하면서 느꼈다.

🐳 8. 에러로그 블로깅

  1. typescript sequelize / create error
    데이터베이스에 새로운 데이터를 넣으려고 하자 에러가 발생했다.
    -> 예전 문법을 최신 문법으로 변경해서 해결했다.

  2. nestJS / 'cannot find module' when test
    테스트를 돌리는데 import 사용했던 부분이 다 없다고 에러가 떴다.
    -> package.json 에서 jest 설정을 변경해서 해결했다.

  3. sequelize/ 로 받아온 데이터 키, 값 지우기
    데이터베이스에서 가져온 데이터를 추가나 변경은 가능했지만 삭제가 되지 않았다.
    -> 데이터를 json 으로 변경한 후에 필요없는 키, 값을 삭제하고 내보냈다.

  4. sequelize /유닛테스트 진행시 데이터베이스 연결
    테스트 진행할 때 findOne 같은 메소드가 실행이 되지 않았다.
    -> 테스트파일에도 데이터베이스 연결을 해줘야 했다.

🐳 9. 후기

  • 아쉬운 점
  1. whitelist 옵션을 사용해보고 싶었는데, 해당 옵션을 사용하니 인자로 들어오는 것들이 모두 필요없다고 넘어와지지 않았다. 추후에 보완하고 싶다.
  2. e2e 테스트까지 진행하고 싶었는데, 과부하 올 것 같아서 우선 이렇게 마무리한다. 유닛테스트 짧게 끝날 줄 알았는데, 생각보다 더 엄청났다.
  3. 변수이름을 더 직관적으로 짓고 싶다.
  • 좋았던 점
  1. express + sequelize 과 nestjs + sequelize 의 연결이 많이 달랐다. 새로운 연결방법을 알게 되어서 좋았다.
  2. 강의를 보고 따라하면 이미 검증된 코드라서 에러처리 경험을 쌓는 기회가 적다. nestjs 로 직접 리팩토링 하면서 다양한 에러를 겪을 수 있어서 좋은 경험이 됐다.
  3. 교육기관에 의해서가 아닌 나 스스로 새로운 스택을 배우고 사용해봐서, 새로운 스택에 대한 자신감이 생겼다.

nestJS 사용하면서 미완성된 코드에 빨간줄이 계속 떠있으니까 짜증스러웠다.
하지만 해당 함수를 다른 곳에서 사용할 때, 타입과 어떤 파라미터를 써야하는지 알려줘서 편리했다.
구현을 하면서 ts 를 더 깊게 공부해야겠다고 생각했다.
아직 인터페이스나 타입설정에 관해 능숙하지 못해서, 리턴값에 대한 타입정의를 빼먹은 부분이 많다.
공부를 하면 할 수록 내가 부족하다는 걸 더 느끼게 된다.
잘 해내고 싶다.

profile
기록하는 백엔드 개발자

0개의 댓글