Post

마트료시카 객체 정제하기: 모든 필드를 명시적으로 써야 해요?

240825 TIL | 데이터소스가 사기인데 왜 레포 주입해서 쓰는거임?

마트료시카 객체 정제하기: 모든 필드를 명시적으로 써야 해요?

🪆 마트료시카 객체 정제하기: 모든 필드를 명시적으로 써야 해요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  /**
   * 게시물의 목록을 조회합니다. content는 20자 까지만 조회됩니다.
   * @param queries
   * @returns 게시물 목록. 없는 경우 빈 배열
   */
  async findAll(hashtag: string, queries: PostingQueryDto): Promise<PostingResponseDto[]> {
    const rawPostings = await this.getRawPostings(hashtag, queries);

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

      return posting;
    });
  }
  
  // 조인으로 인한 hashtag의 depth를 정리한 새 객체를 반환합니다.
  private getPostingObjWithHashtags(postingObj: Posting): PostingResponseDto {
    const hashtags: string[] = postingObj.postingHashtags.map((postingHashtag) => postingHashtag.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,
    };
  }

전체 코드 보러가기

코드 설명

findAll()

  • 게시물 목록 조회 API로, hashtagname을 가져오기 위해 getRawPostings()를 통해 posting-posting_hashtag-hashtag 2번의 조인을 한다.
  • 그래서 rawPostings엔 이러한 형태의 데이터가 담겨서 온다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    posting {
      postingHashtags: [
        {
          hashtag: {
          name: '원하는 값',
          },
        },
        {
          hashtag: {
          name: '원하는 값',
          },
        },
      ]
    }
    

getPostingObjWithHashtags()

  • 원하는 데이터 형태는 이렇기 때문에, 이를 위해 객체의 depth를 정리해주는 private 함수이다.
    1
    2
    3
    
    posting {
      hashtags: ['원하는 값', '원하는 값'],
    }
    

고민: return

  • 한 필드(hashtags)만 바꾸면 되는데 저 많은 필드들을 다시 적어줘야 한다는 게 번거롭다고 느꼈다.
  • 함수의 return type도 정확히 명시하고 있어 굳이 저렇게 쓰지 않아도 괜찮을 것 같았다.

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

delete 연산자

postingHashtagshashtags로 바뀌어야 하니, 처음엔 단순히 이렇게 생각했다.

1
2
3
4
delete postingObj.postingHashtags;
postingObj['hashtags'] = hashtags;

return postingObj;
  • 그러나 타입 안정성의 이유로 TS Error가 나 delete를 쓰지 못했고,
  • postingObj는 이미 Posting 타입의 변수이기 때문에 동적으로 프로퍼티를 추가하는 방법 또한 먹히지 않아서,
  • 무조건 새 객체를 생성해야 하는 상황!!!

spread 연산자

그래서 이렇게 작성해봤다.

1
2
3
4
5
const { postingHashtags, ...postingValues } = postingObj;
return {
  ...postingValues,
  hashtags,
}
  • 반환 값에 spread operator를 사용하지 않는 게 팀 컨벤션이었기 때문에, 이런 방법도 있다~ 하고 넘겼다.

결론

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

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

🤔 데이터소스가 사기인데 왜 레포 주입해서 쓰는거임?

의문

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

결론

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

by GPT4o

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

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