퀴즈 관리 & 문제 은행
Source: Dev.to
Data Model
- One Quiz는 여러 Questions를 가집니다
- One Question는 여러 Options를 가집니다
- One Option은 정답으로 표시될 수 있습니다
이러한 중첩 관계는 초기 데이터 생성 시에는 단순해 보이지만, 편집 단계에 들어가면 훨씬 복잡해집니다.
복잡한 데이터 편집
일반적인 편집 워크플로우는 다음과 같습니다:
- 교사가 퀴즈 편집기를 엽니다.
- 퀴즈 제목을 변경합니다.
- 질문 #2를 삭제합니다.
- 질문 #5의 텍스트를 편집합니다.
- 마지막에 새 질문을 추가합니다.
모든 변경 사항은 Save 버튼 하나로 백엔드에 전송됩니다. 백엔드는 이러한 수정 사항을 어떻게 적용할지 결정해야 합니다.
접근 방식
| 접근 방식 | 장점 | 단점 |
|---|---|---|
| Partial Update (Diffing) | 변경된 부분만 업데이트합니다. | 복잡하고 유지 보수가 어렵으며 중첩 관계에서 버그가 발생하기 쉽습니다. |
| Full Replacement | 구현이 간단합니다. | 데이터 참조가 손실될 위험이 있습니다. |
수정된 전체 교체 (Academic Suite)
Academic Suite는 단순성, 데이터 일관성 및 안전성을 균형 있게 유지하기 위해 데이터베이스 트랜잭션으로 감싼 수정된 전체 교체 방식을 사용합니다. 전체 업데이트 과정은 원자적으로 수행되며, 어느 단계에서든 실패하면 모든 변경 사항이 롤백됩니다.
UpdateQuiz 구현 (handlers/quiz.go)
// Go (gorm)
err := database.DB.Transaction(func(tx *gorm.DB) error {
// 1. Update main quiz data (title, duration, etc.)
if err := tx.Save(&quiz).Error; err != nil {
return err
}
// 2. Delete all old questions
if err := tx.Delete(&models.Question{}, "quiz_id = ?", id).Error; err != nil {
return err
}
// 3. Re‑insert questions from request
for _, q := range req.Questions {
q.QuizID = id
if err := tx.Create(&q).Error; err != nil {
return err
}
}
return nil
})
결과
- 질문 ID가 매 편집마다 변경됩니다.
answers또는attempts테이블이question_id를 참조하고 있다면, 학생 성적 데이터가 손상될 수 있습니다.
운영 시 안전 장치
- Active 상태이거나 Attempts가 있는 퀴즈는 잠금(동결)되어야 합니다.
- 또는 질문에 대해 soft delete 또는 versioning을 사용하여 시험 기록을 보존할 수 있습니다.
Excel 질문 가져오기
웹 양식으로 수십 개의 질문을 입력하는 것은 비효율적이므로 Academic Suite는 Excel (.xlsx) 파일에서 가져오는 기능을 제공합니다.
- 사용 라이브러리:
github.com/xuri/excelize/v2
데이터 검증 문제점
Excel 파일은 자유 형식입니다:
- 열이 비어 있을 수 있습니다.
- 서식이 일관되지 않을 수 있습니다.
- 정답이 잘못된 위치에 있을 수 있습니다.
백엔드는 각 행을 개별적으로 검증합니다:
// Go
for i, row := range rows {
if len(row) < 7 {
errors = append(errors, fmt.Sprintf(
"Row %d: Incomplete columns",
i+1,
))
continue
}
// Further parsing and validation...
}
장점
- 유효한 행은 그대로 저장됩니다.
- 문제 있는 행은 사용자에게 구체적으로 보고되어, 데이터 일관성을 해치지 않으면서 경험을 향상시킵니다.
효율적인 데이터 검색
퀴즈와 그에 포함된 모든 질문 및 답변 옵션을 표시하기 위해 Academic Suite는 GORM의 eager loading을 사용하여 N+1 쿼리 문제를 방지합니다.
// Go (gorm) – eager loading
database.DB.
Preload("Questions").
Preload("Questions.Options").
First(&quiz, "id = ?", id)
eager loading을 사용하면 질문 수가 많은 퀴즈에서도 성능이 안정적으로 유지됩니다.
요약
이 장에서는 안정적이고 확장 가능한 Question Bank와 Quiz Management 시스템의 기반을 구축했습니다. 주요 내용:
- 중첩 업데이트의 과제.
- 데이터 일관성을 유지하기 위한 트랜잭션 전략.
- 진행 중인 퀴즈를 편집할 때의 위험 및 완화 기법.
- 행 수준 검증이 포함된 사용자 친화적인 Excel 가져오기.
- 최적 성능을 위한 즉시 로딩(eager loading) 기법.
다음: Chapter 5에서는 Exam Engine을 다루며, 타이머 설정, 시험 상태, 정밀 채점 메커니즘을 살펴볼 예정입니다.