After the authentication and authorization system is implemented, the primary activity in a Learning Management System (LMS) is creating and managing exam content. At this stage, the system begins to handle complex and interrelated data.
In this chapter, we will discuss how Academic Suite manages quizzes, questions, and answer options on the backend, including advanced CRUD design challenges often encountered in real-world applications.
4.1 Relationship Model of Quiz, Question, and Option
Conceptually, the quiz data structure can be described as follows:
- One Quiz has many Questions
- One Question has many Options
- One Option can be marked as the correct answer
These nested relations appear simple during initial data creation but become significantly more complex when entering the editing process.
4.2 Main Challenge: Nested Updates
One of the biggest challenges in CRUD applications is handling Complex Data Editing features.
Imagine the following scenario:
- A teacher opens the quiz editor
- Changes the quiz title
- Deletes question number 2
- Edits the text of question number 5
- Adds a new question at the end
All these changes are sent to the backend in a single click of the Save button.
The question is: how should the backend handle this condition?
Two Common Approaches
- Partial Update (Diffing) The backend tries to compare the old data and new data, then only updates the changed parts.
* Complex
* Hard to maintain
* Prone to bugs in nested relations
- Full Replacement The backend deletes all old data and then re-inserts the new data.
* Simple implementation
* Risk of losing data references
4.3 Academic Suite Strategy: Modified Full Replacement
Academic Suite uses a Modified Full Replacement approach wrapped in a database transaction.
The goal is to maintain a balance between:
- Implementation simplicity
- Data consistency
- Safe saving process
The entire update process is performed atomically: if one step fails, all changes are rolled back.
UpdateQuiz Implementation
This logic is implemented in handlers/quiz.go:
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
tx.Delete(&models.Question{}, "quiz_id = ?", id)
// 3. Re-insert questions from request
for _, q := range req.Questions {
q.QuizID = id
tx.Create(&q)
}
return nil
})
This approach ensures that the stored quiz structure is always consistent with the data sent by the frontend.
Critical Note: Risk of Question Deletion
The delete and recreate approach has important consequences:
- Question IDs will change every time the quiz is edited
- If the
answersorattemptstables referencequestion_id, student grade data can be corrupted
Best Practice
In a production system:
- Quizzes that are already
Activeor haveAttemptsshould be locked (frozen) - Another alternative is to use soft delete or versioning for questions
This approach prevents changes to the quiz structure that could damage exam history.
4.4 Question Import Feature from Excel
To increase teacher productivity, Academic Suite provides a question import feature from Excel files.
Typing dozens of questions via a web form is inefficient, so the .xlsx format becomes a practical solution.
Library used:
-
github.com/xuri/excelize/v2
Excel Data Validation Challenges
Excel files are very free-form:
- Columns can be empty
- Formatting can be inconsistent
- Correct answers can be in the wrong position
Therefore, the backend performs validation row by row.
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
}
This approach allows:
- Valid rows to still be saved
- Problematic rows to be reported specifically to the user
The user experience becomes better without sacrificing data consistency.
4.5 Data Fetching with Eager Loading
To display a quiz along with all questions and answer options, Academic Suite uses the eager loading feature from GORM.
// Fetch Quiz + Questions + Options in a single query
database.DB.
Preload("Questions").
Preload("Questions.Options").
First(&quiz, "id = ?", id)
This approach prevents the N+1 Query Problem, where the application has to run many additional queries for interrelated data.
With eager loading, application performance remains stable even if the number of questions in the quiz is quite large.
Chapter Summary
In this chapter, we built the foundation of a stable and scalable Question Bank and Quiz Management system.
We discussed:
- Challenges of nested updates
- Transaction strategies to maintain data consistency
- Risks of editing active quizzes
- User-friendly Excel import features
- Eager loading techniques for optimal performance
In Chapter 5, we will enter the heart of the online exam system: the Exam Engine, including timer settings, exam status, and precise grading mechanisms.
Top comments (0)