본문 바로가기

Project

[간단한 퀴즈 서비스] Day05-08 DB 없이 동작 구현

 

빠른 api 구현을 하여 FE와 피드백을하며 서비스를 발전시키는 방향으로 프로젝트 가닥을 잡았기에,

DB 구축하여 퀴즈 데이터를 저장하는 것이 아닌 퀴즈 데이터를 서버 메모리(data 배열에 json형태로 퀴즈 데이터 저장)에 올려서 구현하는 방식을 시도해보았다.

 

퀴즈 데이터를 단어, 정의, 초성, 단어 길이 항목으로 구성하여 배열에 저장하였다.

// 메모리에 데이터를 저장할 변수
let data = [];

// 엑셀 파일 읽기 및 데이터 정제 함수
const loadData = async (filePath) => {
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(filePath);
  const worksheet = workbook.getWorksheet(1);

  data = []; // 데이터를 초기화

  worksheet.eachRow((row, rowNumber) => {
    if (rowNumber > 1) {
      // 첫 번째 행은 헤더로 가정
      data.push({
        word: row.getCell(1).value,
        definition: row.getCell(2).value,
        initialConstant: getInitialConsonants(row.getCell(1).value),
        wordLength: row.getCell(1).value.length,
      });
    }
  });

  console.log("엑셀 데이터를 성공적으로 로드했습니다.");
};

 [
    {
      "word": "진짜",
      "definition": "다른 것을 본뜨거나 거짓으로 만들어 낸 것이 아닌 것.",
      "initialConstant": "ㅈㅉ",
      "wordLength": 2
    },
    ...
    {
      "word": "탑",
      "definition": "여러 층으로 또는 높고 뾰족하게 세운 건축물., 불교에서, 석가모니의 유골을 모시거나 공덕을 기리기 위하여 세우는, 주로 여러 층으로 된 뾰족한 건축물.",
      "initialConstant": "ㅌ",
      "wordLength": 1
    }
  ]

 

 

 

퀴즈 문제 1개 세트를 10개 문제로 구성하였다

// 랜덤으로 10개의 문제 생성 함수
const generateQuizSet = () => {
  if (data.length === 0) {
    throw new Error(
      "데이터가 로드되지 않았습니다. loadData를 먼저 호출하세요."
    );
  }

  const shuffledData = data.sort(() => 0.5 - Math.random());
  const selectedData = shuffledData.slice(0, 10);

  return { quizzes: selectedData };
};

{

  "quizzes": [
    {
      "word": "진짜",
      "definition": "다른 것을 본뜨거나 거짓으로 만들어 낸 것이 아닌 것.",
      "initialConstant": "ㅈㅉ",
      "wordLength": 2
    },
	...
    {
      "word": "탑",
      "definition": "여러 층으로 또는 높고 뾰족하게 세운 건축물., 불교에서, 석가모니의 유골을 모시거나 공덕을 기리기 위하여 세우는, 주로 여러 층으로 된 뾰족한 건축물.",
      "initialConstant": "ㅌ",
      "wordLength": 1
    }
  ]
}

 

 

문제점과 고려 사항

작업을 하다보니 떠오른 문제점들과 추가로 고려해야할 사항들이 여럿 발생하였다.

 

1. 퀴즈 문제 세트를 제공할때 정답도 같이 보내서 유저가 api를 열어보면 퀴즈의 정답을 바로 알 수 있다. api 응답 값 형태가 어떤 문제의 정답인지 알아보기 매우 쉬운 구조라서 쉽게 정답이 유출된다. 정답 여부를 따로 분리한다면 퀴즈 1문제 풀고나서 채점을 요청해야하므로 트래픽이 증가하는 문제 발생하였다.

 

 퀴즈 문제 api에서 정답을 제거하는 방향으로 갈 때, 현재 api 스팩에서 변화가 적게 구현하는 방법은 POST로 메서드를 바꾸는 것이라 생각했다. 하지만 서버에 무언가 값을 바꾸는 것도 아니고 단지 퀴즈 데이터를 요청해서 받아오는 작업이므로 POST를 쓰는 것은 자연스럽지 않다. 결국 GET 메서드를 쓰는 것이 자연스러우며, GET으로 한다면 채점을 위해 문제 번호들을 query parameter에 남길 것인지, 아니면 문제 세트 번호를 넘길 것인지, 문제 세트 번호를 넘긴다면 서버에 해당 번호를 기록해야 했다는 점이 고려되었다.

 

 

2. 단어 뜻이 여러 개 있는 경우가 있다. 이런 경우 단어 뜻을 서버에 1개만 남길지 여러 개 저장할지 고려가 필요하였고 프론트 엔드에서는 유저에게 단어 뜻을 어떻게 보여주어야할지도 고려해야 했다. 단순한 국어 어휘 퀴즈가 아닌 상식 퀴즈라거나 사자성어, 속담 등 새로운 유형의 문제들이 추가될 수도 있다. 확장성을 고려 해야하는 점이 고민되었다

 

 

3. 화면에서 퀴즈 문제 넘어갈 때 여러 가지 동작을 하는 것(문제 채점, 다음 문제로 넘어가기, 마지막 문제의 경우 결과 제출하고 메인으로 가기)에 대해서도 고민이 되었다. 이 부분은 프론트엔드에서 작업될 내용이지만 사용자 관점에서의 편의성과 구현자 입장에서 일관성 있는 서비스 구현을 위해 고민 되었다.

 

 

결론

서비스의 목적과 정답을 알 수 도 있는 것의 민감도에 따라 구현여부가 달라진다 생각하였고 간단하게 단어 퀴즈만 푸는 것이며, 상금이나 퀴즈 결과가 자격 증명에 해당되지는 않으므로 1차 개발에서는 정답과 api에 같이 포함하여 보내기로 정하였다.

 

단어 뜻이 여러 개 있는 경우 서버에 그대로 저장하고 프론트엔드에서 화면에 보여질 수 있는 적정 길이에 맞게 보여주는 것으로 하였다. 하나의 뜻이 너무 긴 경우 스크롤바를 이용하여 한정된 공간 안에 보여지도록 하였다.

 

퀴즈 문제 넘어갈 때는 우선 정답 확인을 해야 다음 문제로 넘어갈 수 있게 채점과 다음 문제로 넘어가기를 분리하였다.