반응형

User와 News 사이에 즐겨찾기(Favorite)기능을 구현하고자 한다.

ManyToMany 관계를 설정하고

특정 API에 user와 news 정보를 보내면 관계가 추가되는 기능을 구현해야 한다.

 

1. 관계를 설정하고 (기본, lazy, eager 설명)

2. 기능을 구현하기까지 과정을 정리해보겠다. (lazy 관계 사용)

 

 

 

ManyToMany 관계 설정

관계는 아래와 같이 설정했다.

// user.entity.ts
  @ManyToMany(() => News, (news) => news.favorites)
  @JoinTable()
  favorites: Promise<News[]>;
// news.entity.ts
  @ManyToMany(() => User, (user) => user.favorites)
  favorites: User[]

여기서 드는 의문은,

favorites의 타입이 왜 Promise 형태인지이다. (이는 lazy 설정을 사용하기 위함이다!)

 

TypeORM에서 ManyToMany를 구현하는 형식은 세 가지가 있는데,

1. 기본 설정

2. lazy 관계

3. eager 관계 이다.

 

아래에서 각각의 설정에 대해 설명해보고, 필자가 왜 lazy 관계를 사용했는지도 설명할 것이다.

 

 

 

1. 기본 설정

기본 설정은 아래와 같이 관계를 설정한다.

// user.entity.ts
  @ManyToMany(() => News, (news) => news.favorites)
  @JoinTable()
  favorites: News[];
// news.entity.ts
  @ManyToMany(() => User, (user) => user.favorites)
  favorites: User[]

위와 같은 관계에서,

User를 초기 생성할 때 favorites을 선언하는 케이스는 예시가 많이 나온다.

 

const category1 = new Category()
category1.name = "animals"
await dataSource.manager.save(category1)

const category2 = new Category()
category2.name = "zoo"
await dataSource.manager.save(category2)

const question = new Question()
question.title = "dogs"
question.text = "who let the dogs out?"
question.categories = [category1, category2]
await dataSource.manager.save(question)

위의 예시는 TypeORM docs에서 발췌한 코드인데,

역시 선언하고 저장하는 형식이다.

 

그런데.. 이미 저장된 favorites을 불러와 정보를 추가하는 로직은

도저히 예시를 찾을 수가 없는 것이다.

그러다 발견한 것이 아래 방식이다!

 

 

 

 

2. eager 관계

// user.entity.ts
  @ManyToMany(() => News, (news) => news.favorites, {
    eager: true,
  })
  @JoinTable()
  favorites: News[];
// news.entity.ts
  @ManyToMany(() => User, (user) => user.favorites)
  favorites: User[]

위와 같이 관계를 설정하면,

어느 한 쪽 정보를 조회 시, favorites에 리스트로 저장된 반대편 정보도 함께 조회된다.

 

하지만, 필요하지 않은 경우에도 (많을지도 모르는 양의) 반대편 정보를 조회해야 한다는 단점이 있다.

 

 

 

 

 

3. lazy 관계

// user.entity.ts
  @ManyToMany(() => News, (news) => news.favorites)
  @JoinTable()
  favorites: Promise<News[]>;
// news.entity.ts
  @ManyToMany(() => User, (user) => user.favorites)
  favorites: User[]

 

lazy 관계를 맺으면, 평소에는 서로의 정보를 저장하지 않고

참조 시에만 불러오는 형식을 취한다.

 

즉, User의 입장에서

favorites을 조회하지 않는(News를 참조하지 않는) 경우에는 favorites에 정보가 저장되지 않고,

favorites을 조회하는(News를 참조하는) 경우에는 favorites에 정보가 저장된다.

 

이 정보는 user_favorite_news라고 하는 새로 생성된 Join 테이블에서 가져 온다.

 

lazy 관계를 사용하는 방식은, 위에서처럼 관계를 Promise로 정의하면 된다.

꺼내서 사용하는 경우에도 Promise로 다뤄줘야 하는데,

이는 아래에서 기능을 구현할 때 다시 언급하겠다.

 

 

 

 

기능 구현!

위에서 언급한 대로,

User가 가지고 있는 favorites(News의 리스트)에 원하는 News를 넣는 기능을 구현해야 한다.

그래서 아래와 같이 구현했다!

 

// user.repository.ts
    async addFavoriteNews(user: User, news: News): Promise<User> {
      const favorites = await user.favorites;
      favorites.push(news);
      user.save();
      return user;
    }
    
    async deleteFavoriteNews(user: User, news: News): Promise<User> {
      let favorites = await user.favorites;
      const newsId: number = news.id;

      user.favorites = Promise.resolve(
        favorites.filter((news) => {
          return news.id !== newsId;
        }),
      );

      user.save();
      return user;

    }

user.favorites은 Promise 객체이므로 await으로 불러와야 함에 주의해야 한다.

또한, 위와 같이 수정 시에도 Promise.resolve 형태로 사용해야 한다!

 

그 점만 주의한다면,

- 참조 시에만 데이터를 불러와 경제적이고,

- API 호출 시마다 관계를 추가할 수 있는

좋은 기능을 구현할 수 있었다!

 

위에서 return한 user를 response하면 아래와 같다!

{
    "status": 200,
    "success": true,
    "message": "좋아하는 뉴스 토글 성공",
    "data": {
        "socialId": "123",
        "nickname": "test",
        "__favorites__": [
            {
                "title": "Test title3",
                "category": "사회",
                "id": 3
            },
            {
                "title": "Test title4",
                "category": "연예",
                "id": 4
            }
        ],
        "__has_favorites__": true
    }
}

(status, success, message는 임의로 정한 규격이니 무시해도 좋다.)

__favorites__, __has_favorites__라는 속성으로 표현된다!

 

뭐니뭐니해도 공식문서가 짱이다.

 

 

 

 

참고 자료 :

https://orkhan.gitbook.io/typeorm/docs/eager-and-lazy-relations

 

Eager and Lazy Relations - typeorm

Note: if you came from other languages (Java, PHP, etc.) and are used to use lazy relations everywhere - be careful. Those languages aren't asynchronous and lazy loading is achieved different way, that's why you don't work with promises there. In JavaScrip

orkhan.gitbook.io

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts