Post

새로운 객체 생성 방법 | 글자 수 제한을 어디서 해야할까요? | 데이터소스가 사기인데 왜 레포 주입해서 쓰는거임?

social-feed-hub의 게시물 목록 조회, 상세 조회 API를 구현하며, 더 좋은 방법이 없을까 하는 것들

새로운 객체 생성 방법 | 글자 수 제한을 어디서 해야할까요? | 데이터소스가 사기인데 왜 레포 주입해서 쓰는거임?

1. 새로운 객체 생성 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  // 조인으로 인한 hashtag의 depth를 정리한 새 객체를 반환합니다.
  private getPostingObjWithHashtags(postingObj: Posting): PostingResponseDto {
    const hashtags: string[] = [];
    if (postingObj.postingHashtags.length) {
      postingObj.postingHashtags.forEach((v) => {
        hashtags.push(v.hashtag.name);
      });
    }
    
    return {
      id: postingObj.id,
      contentId: postingObj.contentId,
      type: postingObj.type,
      title: postingObj.title,
      content: postingObj.content,
      writer: postingObj.writer,
      hashtags,
      viewCount: postingObj.viewCount,
      likeCount: postingObj.likeCount,
      shareCount: postingObj.shareCount,
      createdAt: postingObj.createdAt,
      updatedAt: postingObj.updatedAt,
    };
  }

코드 설명

  • 게시물 목록 조회 API로, hashtagname을 가져오기 위해 posting-posting_hashtag-hashtag 2번의 조인을 한다.
  • 그래서 처음 쿼리를 날렸을 땐 이러한 형태의 데이터가 온다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    posting {
      postingHashtags: [
        {
          hashtag: {
          name: '원하는 값',
          },
        },
        {
          hashtag: {
          name: '원하는 값',
          },
        },
      ]
    }
    
  • 원하는 데이터 형태는 이렇기 때문에, 이를 위해 depth를 정리해주는 함수이다.
    1
    2
    3
    
    posting {
      hashtags: ['원하는 값', '원하는 값'],
    }
    

문제라고 느끼는 점: return

  • 문제까진 아니지만… 딱 한 필드만 바꾸면 되는데 저 많은 필드들을 다시 적어줘야 한다는 게 번거롭다고 느꼈다.
  • 함수의 return type도 정확히 명시하고 있고, 한 필드의 값만 바꾸면 되어서 굳이? 싶음

나름대로 생각해본 다른 방안

  • postingHashtagshashtags로 바뀌어야 하니, 처음엔 단순히 이렇게 생각했다.
    1
    2
    3
    4
    5
    
    ...
    delete postingObj.postingHashtags;
    postingObj['hashtags'] = hashtags;
    
    return postingObj;
    
    • 그러나 delete의 피연산자는 optional한 필드여야 하고,
    • postingObj는 이미 Posting 타입의 변수이기 때문에 동적으로 프로퍼티를 추가하는 방법이 먹히지 않아서,
    • 무조건 새 객체를 생성해야 하는 상황!!!
  • 그래서 이렇게 작성해봤다.
    1
    2
    3
    4
    5
    6
    
    ...
    const { postingHashtags, ...postingValues } = postingObj;
    return {
      ...postingValues,
      hashtags,
    }
    
    • 그렇지만 반환 값에 spread operator를 사용하지 않는 게 팀 컨벤션이었기 때문에, 그냥 이런 방법도 있다~ 하고 넘겼다.

결론

팀 회의 시간에 고민을 발표하며 얻은 결론이다.

  • 타입스크립트는 개발자를 위한 일종의 도구이기 때문에, 타입 시스템은 컴파일 시점에만 작동하며 런타임에선 적용되지 않는다.
  • 그래서 postingValues에 원하는 값만 있을지는 혹시 모르는 일이다. 따라서 저렇게 명시적으로 필드를 적어주는 게 안전하고 좋다. 귀찮아도!

2. 글자 수 제한을 어디서 해야할까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  ...
  // 입력된 값대로 정렬해 페이지네이션
  const postings = await queryBuilder
    .orderBy(`posting.${orderBy}`, sortOrder)
    .skip(page * pageCount)
    .take(pageCount)
    .getMany();

  // hashtag depth 정리 & content 글자 수 제한 적용해 반환
  return postings.map((posting) => {
    const arrangedPosting = this.getPostingObjWithHashtags(posting);
    arrangedPosting.content = arrangedPosting.content.substring(0, 20);

    return arrangedPosting;
  });

코드 설명

  • 전체 코드 보기
  • 게시물 목록 조회 API에서, 쿼리 파라미터에 따른 디비 쿼리를 날리고 가져온 데이터 객체를 정제해 넘겨주는 부분이다.
  • 1번에서 설명한 getPostingObjWithHashtags 함수로 hashtag의 depth를 정리하고, content에 글자 수 제한을 적용한다.

문제라고 느끼는 점

  • 현재 코드에선 일단 content를 모두 불러온 뒤, map을 돌려 20자 제한을 적용하고 있다.
  • 20자 이후의 데이터들은 불필요한데도 불구하고 DB에서 가져오고 있기 때문에 비효율적이라는 생각이 들어 이것보다 더 좋은 방법이 있는지 궁금했다.

결론

쿼리단에서 하는 방법도 있을 것 같아 찾아보았다.

  • SELECT 할 때 SUBSTRING() 함수를 사용하는 방법
    1
    2
    3
    4
    5
    6
    
    const postings = await queryBuilder
      .select([
        'posting.id', 
        `SUBSTRING(posting.content, 1, 20) AS content`
      ])
      .getMany();
    
    • 예시 코드이다. MySQL에선 LEFT(), PostgreSQL에선 substring() 이라는 같은 결과를 내는 함수를 활용할 수도 있다고 한다.
  • 이 방법을 사용하려면 leftJoinAndSelect()로 간단히 모든 필드를 SELECT 했을 때와 다르게 모든 필드를 명시적으로 select()에 넣어줘야 한다.
  • 어떻게 돌아가는 개념인지는 알겠지만 잘 작동이 되지 않아 일단 덮어두었다..^^

3. 그냥 의문점: 데이터소스가 사기인데 왜 레포 주입해서 쓰는거임?

의문

  • Module 파일에 TypeOrmModule.forFeature([]) import 안 해도 그냥 써짐
  • 멤버변수에 Repo 줄줄이 달지 않고 DataSource 변수만 선언해준 뒤 getRepository(${Entity}) 하면 되어서 뭔가 깔끔하게 보임

결론

  • Repository를 직접 주입해서 쓰는 경우
    • 도메인, 관심사 분리
    • DI 컨테이너가 미리 의존성 주입 시켜놔서 쓰기 편하다.
    • 테스트, 유지보수가 비교적 쉽다.
  • DataSource를 사용하는 경우
    • 복잡한 쿼리를 작성하거나 특정 엔티티에 종속되지 않은 로직이 필요할 때 사용한다.
    • 같은 패턴을 반복해야 해 번거로울 수 있다.

by GPT4o

DataSource는 한 단계 상위 개념인 것 같아서, 지금 수준의 코드를 짤 땐 굳이 안 쓸 것 같다.

This post is licensed under CC BY 4.0 by the author.