[UniLetter] 서버 페이징 리팩토링 (TypeScript + TypeORM)

Seohyun-kim·2022년 11월 25일
0
post-thumbnail

1. 기존 API 구성과 문제점


1.1 pageNum과 paseSize를 입력하지 않았을 때

  • query값을 주지 않으면 400 ERROR가 발생하게 했음.
  • 페이징을 무조건 적용하는 형태로 호출한다고 생각했으나
  1. 이전 버전의 기기에서 실행하는 경우 호환성 문제 발생
  2. 기존 API를 두고, 페이징 적용되는 새로운 API를 만들면
    페이징 해야하는 이벤트 GET API가 6개나 있었음

기존 API를 유지하되, 페이징 관련 변수는 Optional로 주고
값이 안들어오는 경우는 전체를 내려주자!


1.2 Response type

  • 기존 응답 스케마

    export const EventPageResponseScheme = {
      maxPage: z.number(),
      totalEvent: z.number(),
      event: z.object(EventResponseScheme),
    
    };
    • maxPage : 최대 페이지 수 (totalEvent/pageSize 올림값)
    • totalEvent : 총 내려줘야 할 이벤트 갯수
    • event : 이벤트 object
  • 모바일 단에서 딱히 maxPagetotalEvent에 대한 정보가 필요 없다고 한다.
    maxPage가 되기전까지만 호출한다고 생각했으나
    그냥 Event 배열에 새로 내려주는 부분을 추가하는 형태라고 한다.
    (서버입장에서도 이게 굳)

그냥 페이징 없었던 때 처럼 Event[] 형태로 내려주는게 낫다!
기존에 있던 EventResponseScheme을 사용함.
(여기서는 event: z.object(EventResponseScheme) 하나만들어간 형태)



2. Define Router

2.1 client에서 사용하고있던 그대로 /events로 바꾸자

  • 원래 /events 는 전체 이벤트 내려주는 형태이고
    /events-by-page를 새로만들어 페이징 적용 후 내려주는 형태였는데
    위의 1.1의 이슈로 페이징 적용 API를 /events로 바꾸고,
    기존에 전체 다 내려주는 API는 /event-old로 바꿈!

    • /events -> `/event-old
    • '/events-by-page' -> /events
  • 1.2 이슈에 따라 응답하는 형태도 Event[] 로 통일함

routes/events/getEventsbyPage.ts

export default defineRoute('get', '/events', schema, async (req, res) => {
    const {userId} = req;
    const {pageNum, pageSize} = req.query;

    const eventInformation = await EventService.getEventsbyPage(userId, pageNum, pageSize); // 페이징 적용

    return res.json(await Promise.all(eventInformation.map(e => e.toResponse(userId))));
});

2.2 query의 pageNum과, paseSize를 Optional로

  • 위의 1.1의 이슈에 따라 모바일 하위버전에서도 적용되기 위해 옵셔널로 적용.
query: {
  pageNum: stringAsInt.optional(),
    pageSize:stringAsInt.optional()
},

2.3 Response Type

  • 이벤트 배열 형태로 내려줌.
response: [EventResponseScheme]

src/entity/schemes.ts

export const EventResponseScheme = {
  id: z.number(),
  userId: z.number(),
  nickname: z.string(),
  profileImage: z.string().optional().nullable(),

  title: z.string(),
  host: z.string().optional().nullable(),
  category: z.string(),
  target: z.string(),
  startAt: z.date(),
  endAt: z.date().optional().nullable(),
  contact: z.string().optional().nullable(),
  location: z.string().optional().nullable(),

  body: z.string(),
  imageUuid: z.string().optional().nullable(),
  imageUrl: z.string().optional().nullable(),
  createdAt: z.date(),

  /**
   * 추가 속성.
   */
  wroteByMe: z.boolean().optional().nullable(),
  likedByMe: z.boolean().optional(),
  notificationSetByMe: z.boolean().optional().nullable(),
  notificationSetFor: z.string().optional().nullable(),

  comments: z.number(),
  views: z.number(),
  likes: z.number(),
  notifications: z.number(),
}


3. Event Service

src/service/EventService.ts

3.1 getEventsbyPage : 페이지 별로 이벤트 가져옴

  // 페이지 별로 이벤트 가져옴 NEW
  async getEventsbyPage(userId?: number, pageNum?:number, pageSize?:number ): Promise<Event[]> {
    if(pageNum == undefined  || pageSize == undefined) { // 하나라도 비어있으면
      pageNum = 0;
      pageSize = 0; // 전체 가져오는걸로!
    }

    if (userId == null){ // 로그인 X.
      return await this.getEventsRegardlessBlockingsbyPage(pageNum, pageSize); // 비회원은 전부
    }else{ // 로그인 한 사용자.
      return await this.getEventsWithoutBlockedUserbyPage(userId, pageNum, pageSize); // 로그인 한 사람은 blocking user 빼고
    }

  }
  • 회원 비회원 여부 적용완료.

  • 페이징 관련 변수가 들어오지 않는 경우 디폴트로 0을 주어, 전체를 내려주도록 함.

  • 페이징 변수를 0을 만들어 무조건 함수에 넘겨주도록 함으로써,
    2개 만드는 대신에 하나로 통일


3.2 getEventsRegardlessBlockingsbyPage : 차단 사용자 고려하지 않고 이벤트를 페이지 별로 가져오기

  private async getEventsRegardlessBlockingsbyPage(pageNum:number, pageSize:number): Promise<Event[]>  {
    return await Event.find(
        {order: {id: 'DESC'},
          skip: pageSize * pageNum, // 여기 추가됨!
          take: pageSize,}); // 추가됨!
  }
  • find 사용함 (간단한 경우)
  • id 값 역순으로 가져온다.
  • offset (= pageSize * pageNum) 부터 시작해서
  • 한 번에 pageSize 사이즈 만큼!
  • pageSize = 0 이면 그냥 전체 가져오더라!

3.3 getEventsWithoutBlockedUserbyPage : 차단한 사용자 제외하고 내려주기

  • QueryBuilder 사용함 (복잡한 경우)
  • take(pageSize)skip(pageSize * pageNum)을 추가해주었다.
  // 차단된 사용자 제외하고 페이지 별로 내려주기 NEW
  private async getEventsWithoutBlockedUserbyPage(requestorId: number, pageNum:number, pageSize:number ): Promise<Event[]> {
    return await Event.createQueryBuilder('event')
        /** relations 필드 가져오는 부분 */
        .leftJoinAndSelect('event.user', 'user')
        .leftJoinAndSelect('event.comments', 'comments')
        .leftJoinAndSelect('event.likes', 'likes')
        .leftJoinAndSelect('event.notifications', 'notifications')

        /** where 절을 위한 join(select는 안 함) */
        .leftJoin('event.user', 'event_composer')
        .where(`event_composer.id NOT IN (
        SELECT blocked_user_id 
        FROM block
        WHERE block.blocking_user_id = :requestorId
      )`, {requestorId})
        .take(pageSize)
        .skip(pageSize * pageNum) // 페이징 적용
        .orderBy('event.id', 'DESC')
        .getMany();
  }

같은 방법으로 다른 라우터에도 모두 페이징 적용을 해 주었다!!

0개의 댓글