오늘의 Troubleshooting

쁘띠 TFT 프로젝트

파라미터 값 생략?

  • 이번에 프론트엔드(HTML) 쪽 구현을 조금씩 해보시는 팀원분의 의견으로 이야기가 시작된다

  • 기존의 parameter로 받는 userKey 값을 프론트에서 동적으로 생성할 방법을 찾을 수가 없다!

    1. 프론트엔드에서 받을 수 있는 값은 로그인을 한 후 받은 jwt토큰 밖에 없다.

    2. 이론상 프론트엔드와 백엔드는 구별되어 있어야기에 jwt토큰을 복호화(해석)를 쉽게 진행할 수 없었다

    3. 토큰 입력만으로도 유저 기능을 이용할 수 있게 백엔드를 재설계 해야되지 않나..?(parameter 생략)

  • 이후 코드들을 분석해 토큰을 인증하는 미들웨어 에서 user를 특정 가능하기에 생략하여도 문제없다고 결론이 났다!
    ( 이것도 리팩토링이죠 음)

=> params에 있는 userKey들을 다 삭제하며, 인증 미들웨어를 이용해 user 객체 값들을 사용하도록 수정시작!

// 이 부분을 
const { key } = req.params
// 이렇게 인증 미들웨어 에서 받은 user 값을 이용해 사용
const { user } = req
const myAgent = await prisma.myAgents.findFirst({ where: { agentKey: +agent.agentKey, userKey: user.userKey }})

=> +key 를 사용했던 부분을 user.userKey로 전부 수정!

prisma 리팩토링 + 숫자 확인

병합한 파일들을 기준으로 API가 적은 순(user -> match -> agents)으로 리팩토링을 시작했다

주요 수정사항은 prisma를 이용한 값 조회/생성/업데이트 부분에서 쿼리를 여러 번 돌리는 곳이다.
( 관계성을 가진 테이블들은 동시에 처리가 가능하다! )

Prisma 공식 사이트

  • 기존 코드
// [2] 승패가 갈렸을 때
await prisma.$transaction(async (tx) => {
  // [2-1] Ranks 테이블에서, 승자 winCount 1 증가하고 mmr 50 증가
  await tx.ranks.update({
    where: { userKey: win },
    data: { winCount: { increment: 1 }, mmr: { increment: 50 } },
  });
  // [2-2] Ranks 테이블에서, 패자 loseCount 1 증가하고 mmr 20 감소
  await tx.ranks.update({
    where: { userKey: lose },
    data: { loseCount: { increment: 1 }, mmr: { decrement: 20 } },
  });
  // [2-3] Assets 테이블에서, 승자 enhancer 10 증가하고 cash 50,000 증가
  await tx.assets.update({
    where: { userKey: win },
    data: { enhancer: { increment: 10 }, cash: { increment: 50000 } },
  });
  // [2-3] Assets 테이블에서, 패자 enhancer 2 증가하고 cash 10,000 증가
  await tx.assets.update({
    where: { userKey: lose },
    data: { enhancer: { increment: 2 }, cash: { increment: 10000 } },
  });
})
  • 리팩토링 후 코드 (분명 코드 줄은 늘어났으나, 쿼리는 2번으로 줄어들었다)
await prisma.$transaction(async (tx) => {
  // [2-1] 승자 Ranks 테이블에서, 승자 winCount 1 증가하고 mmr 50 증가
  // [2-2] 승자 Assets 테이블에서, 승자 enhancer 10 증가하고 cash 50,000 증가
  await tx.users.update({
    where: { userKey: win },
    data: {
      rank: {
        update: {
          data: {
            winCount: { increment: 1 },
            mmr: { increment: 50 }
          }
        }
      },
      asset: {
        update: {
          data: {
            enhancer: { increment: 10 },
            cash: { increment: 50000 }
          }
        }
      }
    },
  });
  // [2-3] 패자 Ranks 테이블에서, 패자 loseCount 1 증가하고 mmr 20 감소
  // [2-4] 패자 Assets 테이블에서, 패자 enhancer 2 증가하고 cash 10,000 증가
  await tx.users.update({
    where: { userKey: lose },
    data: {
      rank: {
        update: {
          data: {
            loseCount: { increment: 1 },
            mmr: { decrement: 20 }
          }
        }
      },
      asset: {
        update: {
          data: {
            enhancer: { increment: 2 },
            cash: { increment: 10000 }
          }
        }
      }
    },
  });
});
  • 입력값이 숫자인지 확인하는 과정에서 썻던 구문을 다른 팀원이 쓴 걸 보고 수정하였다!
//기존 코드
if (!Number.isInteger(+agentKey))
//팀원분 코드 < 같은 로직이지만 간략해짐
if (isNaN(+agentKey))

챔피언 도감 조회 리팩토링

기존에 있는 챔피언 도감 조회가 정렬 기능 / 필터 기능을 구현하였는데, 이를 최적화 해주는 작업을 진행 하였다.

도감조회리팩1

  • 위의 API 명세서에 따라 입력이 있으면 그에 따라 정렬 / 필터가 되게 설계되어있엇다.
//입력값 확인
const {showHow, showWhat, ordrerBy, orderHow} = req.body
// 허용할 수 있는 orderBy 값들
const validOrderBy = ["name", "position", "grade", "team"]; 
// 허용할 수 있는 정렬 방향
const validOrderHow = ["asc", "desc"]; 
// showHow가 team, position, grade 중 하나인지 체크
const validShowHow = ["team", "position", "grade"];
/* 이후 if와 includes를 통해 valid에 포함되어있는지로 에러 메시지 출력*/

const whereCondition = {
    [showHow]: showWhat, // showHow에 맞는 조건 설정 (team, position, grade)
  };

const orderByCondition = validOrderBy.includes(orderBy)
    ? { [orderBy]: validOrderHow.includes(orderHow) ? orderHow : "asc" }
    : { name: "asc" }; // 기본값은 이름순 정렬

// 동적 쿼리 실행
    const showAgents = await prisma.agents.findMany({
      where: whereCondition,
      select: {
        name: true,
        team: true,
        position: true,
        grade: true,
      },
      orderBy: [orderByCondition], // 동적으로 생성된 orderBy 조건 사용
    });
  • 여기서 validShowWhat 의 값을 특정하기 어려워 값의 존재 여부만 유효성 검사를 한 것을 보고 이를 개선하려 했다.

  • 이 때 동적으로 테이블의 컬럼값을 읽어오기 위해 prisma의 queryRawUnsafe 기능을 사용하였다.

// showWhat 유효성 검사 개선
const validShowWhat = await prisma.$queryRawUnsafe(`SELECT ${showHow} FROM Agents GROUP BY 1`).then((what) => what.map((e) => Object.values(e)[0]))
if (!validShowWhat.includes(showWhat)) return res
  .status(400)
  .json({ errorMessage: `유효하지 않은 showWhat 값입니다. 목록: [${validShowWhat.join(", ")}]`});
  • 추가로 이 값들이 옵션(생략 가능)이기에 if문을 새로 묶어주었다.
//showHow가 있을 경우만
if (showHow) {
  // showHow 유효성 검사
  const validShowHow = ["team", "position", "grade"];
  if (!validShowHow.includes(showHow)) return res
    .status(400)
    .json({ errorMessage: `유효하지 않은 showHow 값입니다. 목록: [${validShowHow.join(", ")}]`});

  // showWhat 유효성 검사
  const validShowWhat = await prisma.$queryRawUnsafe(`SELECT ${showHow} FROM Agents GROUP BY 1`).then((what) => what.map((e) => Object.values(e)[0]))
  if (!validShowWhat.includes(showWhat)) return res
    .status(400)
    .json({ errorMessage: `유효하지 않은 showWhat 값입니다. 목록: [${validShowWhat.join(", ")}]`});

  // 기본 쿼리 설정
  whereCondition = { [showHow]: showWhat }
}
//orderBy가 있을 경우만
if (orderBy) {
  // orderBy 유효성 검사
  const validOrderBy = ["name", "position", "grade", "team"];
  if (!validOrderBy.includes(orderBy)) return res
    .status(400)
    .json({ errorMessage: `유효하지 않은 orderBy 값입니다. 목록: [${validOrderBy.join(", ")}]`});

  // orderHow 유효성 검사
  const validOrderHow = ["asc", "desc"];
  if (orderHow && !validOrderHow.includes(orderHow)) return res
    .status(400)
    .json({ errorMessage: `유효하지 않은 orderHow 값입니다. 목록: [${validOrderHow.join(", ")}]` });

  // 기본값은 이름순 정렬
  orderByCondition = { [orderBy]: orderHow || "asc" }
}
  • 그리고 입력 없이 동적쿼리 실행 시에도 출력이 되도록 초기값을 할당해주었다.
// 기본 값 설정
let whereCondition = { NOT : []}
let orderByCondition = { name: "asc" }
// 동적 쿼리 실행
const showAgents = await prisma.agents.findMany({
  where: whereCondition,
  select: {
    name: true,
    team: true,
    position: true,
    grade: true,
  },
  orderBy: [orderByCondition], // 동적으로 생성된 orderBy 조건 사용
});
return res
  .status(200)
  .json({ data: showAgents });

챔피언 매각 수정

기존의 로직을 리팩토링하는 과정에서 쿼리를 줄이기 위해 users에서 값을 업데이트 했는데..

  • 기존
const asset = await prisma.assets.update({
  where: { userKey: user.userKey },
  data: {
    cash: { increment: +amount }
  }
})
const myAgent = await prisma.myAgents.update({
  where: { userKey: user.userKey },
  data: {
    count: { decrement: +count }
  }
})
//출력
resJson = [
  {
    message: `성공적으로 챔피언 ${agent.name}(을)를 ${count}만큼 판매하였습니다.`,
    amount: `+${amount}`,
    cash: asset.cash
  },
];
  • 리팩토링
const update = await prisma.users.update({
  where: { userKey: user.userKey },
  data: {
    asset: {
      update: {
        data: {
          cash: { increment: +amount },
        },
      },
    },
    myAgent: {
      update: {
        where: { myAgentKey: +myAgent.myAgentKey },
        data: {
          count: { decrement: +count },
        },
      },
    },
  },
});
// 출력값
resJson = [
  {
    message: `성공적으로 챔피언 ${agent.name}(을)를 ${count}만큼 판매하였습니다.`,
    amount: `+${amount}`,
    cash: // 어캐 가져옴??
  },
];
  • 잘 보면 cash 값을 읽어올 수 없어 생략을 했었다..

=> 그래서 방법이 없나 확인해보니 update여도 include를 통해 값을 불러올 수 있다는 것이다!

// 기존 update 쿼리의 where : {},data: {}, 다음에 넣어주어서 읽어왔따!
include: {
  asset: {
    select:{
      cash: true
    }
  }
}
// 출력
resJson = [
  {
    message: `성공적으로 챔피언 ${agent.name}(을)를 ${count}만큼 판매하였습니다.`,
    amount: `+${amount}`,
    cash: update.asset.cash //include 를 통해 가져온 값
  },
];

개선점 분석

  • 리팩토링 과정을 좀 더 기록하면서 했다면 좋았을 것 같다. (생략된 부분이 많았다)

Reference

programmers
스파르타코딩클럽