Low-Level Design: Polling System - Edge Cases

WBOY
Release: 2024-08-31 14:43:33
Original
378 people have browsed it

Low-Level Design: Polling System - Edge Cases

Table of contents

Case 1 - Handle Versioning for Update
Case 2 - PollID to be as UUID & not Primary Key
Case 3 - Empty or Invalid Options
Case 4 - Duplicate Options
Case 5 - Question Length Limit
Case 6 - Poll Expiration

Please refer to the following articles first:

  1. Low-Level Design: Polling System: Basic

  2. Low-Level Design: Polling System - Using Node.js & SQL

Edge Cases Handling

Case 1

To manage updates to a poll's question and options while retaining the previous details associated with the same poll ID, you can implement a versioning system. This approach allows you to keep track of the historical data for each poll, ensuring that old details are preserved even after updates.

Step 1: Database Schema Changes

  1. Update the Polls Table

    • Add a current_version_id column to the polls table to track the latest version of the poll.
  2. Create the Poll Versions Table

    • Create a new table to store historical versions of polls.

Updated Database Schema

CREATE DATABASE polling_system; USE polling_system; CREATE TABLE polls ( poll_id INT AUTO_INCREMENT PRIMARY KEY, current_version_id INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (current_version_id) REFERENCES poll_versions(version_id) ON DELETE SET NULL ); CREATE TABLE poll_versions ( version_id INT AUTO_INCREMENT PRIMARY KEY, poll_id INT, question VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE ); CREATE TABLE options ( option_id INT AUTO_INCREMENT PRIMARY KEY, poll_id INT, option_text VARCHAR(255) NOT NULL, FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE ); CREATE TABLE votes ( vote_id INT AUTO_INCREMENT PRIMARY KEY, poll_id INT, user_id VARCHAR(255) NOT NULL, option_id INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE, FOREIGN KEY (option_id) REFERENCES options(option_id) ON DELETE CASCADE );
Copy after login

Step 2: API Implementation Changes

Update the Poll Controller

Modify the updatePoll method to check if the question has changed before creating a new version.

File: controllers/pollController.js
const pool = require('../db/db'); // Create Poll exports.createPoll = async (req, res) => { const { question, options } = req.body; if (!question || !options || !Array.isArray(options) || options.length < 2) { return res.status(400).json({ message: "Invalid input data. Question and at least two options are required." }); } try { const connection = await pool.getConnection(); await connection.beginTransaction(); const [result] = await connection.execute( 'INSERT INTO polls (current_version_id) VALUES (NULL)' ); const pollId = result.insertId; const [versionResult] = await connection.execute( 'INSERT INTO poll_versions (poll_id, question) VALUES (?, ?)', [pollId, question] ); const versionId = versionResult.insertId; // Update the current version in the polls table await connection.execute( 'UPDATE polls SET current_version_id = ? WHERE poll_id = ?', [versionId, pollId] ); const optionQueries = options.map(option => { return connection.execute( 'INSERT INTO options (poll_id, option_text) VALUES (?, ?)', [pollId, option] ); }); await Promise.all(optionQueries); await connection.commit(); connection.release(); res.status(201).json({ pollId, message: "Poll created successfully." }); } catch (error) { console.error("Error creating poll:", error.message); res.status(500).json({ message: "Error creating poll." }); } }; // Update Poll exports.updatePoll = async (req, res) => { const { pollId } = req.params; const { question, options } = req.body; if (!pollId || !options || !Array.isArray(options) || options.length < 2) { return res.status(400).json({ message: "Invalid input data. At least two options are required." }); } try { const connection = await pool.getConnection(); await connection.beginTransaction(); // Fetch the existing poll const [existingPoll] = await connection.execute( 'SELECT question FROM poll_versions WHERE poll_id = (SELECT current_version_id FROM polls WHERE poll_id = ?)', [pollId] ); if (existingPoll.length === 0) { await connection.rollback(); connection.release(); return res.status(404).json({ message: "Poll not found." }); } const currentQuestion = existingPoll[0].question; // Check if the question has changed if (currentQuestion !== question) { // Create a new version since the question has changed const [versionResult] = await connection.execute( 'INSERT INTO poll_versions (poll_id, question) VALUES (?, ?)', [pollId, question] ); const versionId = versionResult.insertId; // Update the current version in the polls table await connection.execute( 'UPDATE polls SET current_version_id = ? WHERE poll_id = ?', [versionId, pollId] ); } // Remove old options and insert new ones await connection.execute('DELETE FROM options WHERE poll_id = ?', [pollId]); const optionQueries = options.map(option => { return connection.execute( 'INSERT INTO options (poll_id, option_text) VALUES (?, ?)', [pollId, option] ); }); await Promise.all(optionQueries); await connection.commit(); connection.release(); res.status(200).json({ message: "Poll updated successfully." }); } catch (error) { console.error("Error updating poll:", error.message); await connection.rollback(); res.status(500).json({ message: "Error updating poll." }); } }; // Delete Poll exports.deletePoll = async (req, res) => { const { pollId } = req.params; try { const connection = await pool.getConnection(); const [result] = await connection.execute( 'DELETE FROM polls WHERE poll_id = ?', [pollId] ); connection.release(); if (result.affectedRows === 0) { return res.status(404).json({ message: "Poll not found." }); } res.status(200).json({ message: "Poll deleted successfully." }); } catch (error) { console.error("Error deleting poll:", error.message); res.status(500).json({ message: "Error deleting poll." }); } }; // Vote in Poll exports.voteInPoll = async (req, res) => { const { pollId } = req.params; const { userId, option } = req.body; if (!userId || !option) { return res.status(400).json({ message: "User ID and option are required." }); } try { const connection = await pool.getConnection(); const [userVote] = await connection.execute( 'SELECT * FROM votes WHERE poll_id = ? AND user_id = ?', [pollId, userId] ); if (userVote.length > 0) { connection.release(); return res.status(400).json({ message: "User has already voted." }); } const [optionResult] = await connection.execute( 'SELECT option_id FROM options WHERE poll_id = ? AND option_text = ?', [pollId, option] ); if (optionResult.length === 0) { connection.release(); return res.status(404).json({ message: "Option not found." }); } const optionId = optionResult[0].option_id; await connection.execute( 'INSERT INTO votes (poll_id, user_id, option_id) VALUES (?, ?, ?)', [pollId, userId, optionId] ); connection.release(); res.status(200).json({ message: "Vote cast successfully." }); } catch (error) { console.error("Error casting vote:", error.message); res.status(500).json({ message: "Error casting vote." }); } }; // View Poll Results exports.viewPollResults = async (req, res) => { const { pollId } = req.params; try { const connection = await pool.getConnection(); const [poll] = await connection.execute( 'SELECT * FROM polls WHERE poll_id = ?', [pollId] ); if (poll.length === 0) { connection.release(); return res.status(404).json({ message: "Poll not found." }); } const [options] = await connection.execute( 'SELECT option_text, COUNT(votes.option_id) as vote_count FROM options ' + 'LEFT JOIN votes ON options.option_id = votes.option_id ' + 'WHERE options.poll_id = ? GROUP BY options.option_id', [pollId] ); connection.release(); res.status(200).json({ pollId: poll[0].poll_id, question: poll[0].question, results: options.reduce((acc, option) => { acc[option.option_text] = option.vote_count; return acc; }, {}) }); } catch (error) { console.error("Error viewing poll results:", error.message); res.status(500).json({ message: "Error viewing poll results." }); } };
Copy after login

Step 3: Update Poll Routes

Ensure the routes are defined properly in your pollRoutes.js.

File: routes/pollRoutes.js
const express = require('express'); const router = express.Router(); const pollController = require('../controllers/pollController'); // Routes router.post('/polls', pollController.createPoll); router.put('/polls/:pollId', pollController.updatePoll); router.delete('/polls/:pollId', pollController.deletePoll); router.post('/polls/:pollId/vote', pollController.voteInPoll); router.get('/polls/:pollId/results', pollController.viewPollResults); module.exports = router;
Copy after login

Summary of Changes

  1. Database:

    • Updated the polls table to include current_version_id.
    • Created the poll_versions table for tracking question versions.
    • The options and votes tables remain unchanged.
  2. API:

    • Created a new createPoll method to initialize polls and versions.
    • Updated the updatePoll method to check for question changes before creating a new version.
    • Added methods for voting and viewing poll results.
  3. Routing:

    • Ensured all necessary routes are defined to handle poll creation, updates, voting, and results.

Case 2

To handle a scenario where the pollId is required to be a UUID (Universally Unique Identifier).

Here are the steps to implement UUIDs for thepollId in your polling system without providing code:

Steps to Implement UUID for Poll ID

  1. ** Database Schema Update:**

    • Modify thepolls, poll_versions, options, andvotes tables to use CHAR(36) for poll_id instead of an integer.
    • Create a new poll_versions table to store historical versions of poll questions and options linked by the UUID.
  2. ** UUID Generation:**

    • Decide on a method for generating UUIDs.You can use a library or built -in functions in your application environment to create UUIDs.
  3. ** Create Poll Logic:**

    • When creating a new poll, generate a UUID and use it as the poll_id.
    • Insert the new poll record into the polls table.
    • Insert the initial question into the poll_versions table and link it with the generated UUID.
  4. ** Update Poll Logic:**

    • When updating a poll:
  5. Check if the question has changed.

    • If the question has changed, create a new version entry in thepoll_versions table to store the old question and options.
    • Update the polls table with the new question and options as necessary.
  6. ** Voting Logic:**

    • Update the voting mechanism to ensure that it uses the UUID as poll_id.
  7. Validate that the UUID provided in the vote request exists in thepolls table.

  8. ** API Updates:**

    • Modify API endpoints to accept and return UUIDs for poll_id.
    • Ensure that all API operations(create, update, delete, vote) reference the UUID format consistently.
  9. ** Testing:**

    • Thoroughly test the application to ensure that UUIDs are handled correctly in all scenarios(creation, updates, voting, and retrieving poll results).
  10. ** Documentation:**

    • poll_id 형식의 변경 사항과 버전 관리 및 UUID 사용과 관련된 새로운 동작을 반영하도록 API 문서를 업데이트하세요.

이러한 단계를 따르면 데이터 무결성과 기록 추적을 보장하면서 폴링 시스템에서 pollId에 대한 UUID를 성공적으로 구현할 수 있습니다.


사례 3

비어 있거나 잘못된 옵션

검증 접근 방식:

  • API 입력 유효성 검사:API 엔드포인트에 검사를 구현하여 요청 본문에 제공된 옵션이 비어 있지 않고 특정 기준(예: 허용되지 않는 경우 특수 문자 없음)을 충족하는지 확인하세요.
  • 피드백 메커니즘:옵션이 유효하지 않거나 비어 있는 경우 사용자에게 명확한 오류 메시지를 제공하여 입력 내용을 수정하도록 안내합니다.

사례 4

중복 옵션

고유성 검사:

  • 삽입 전 유효성 검사:설문조사에 옵션을 추가하기 전에 데이터베이스의 기존 옵션에 중복이 있는지 확인하세요. 이는 투표 ID를 사용하여 옵션 테이블을 쿼리하고 이를 새 옵션과 비교하여 수행할 수 있습니다.
  • 사용자 피드백:중복된 옵션이 감지되면 의미 있는 오류 메시지를 반환하여 사용자에게 어떤 옵션이 중복되었는지 알려주고 그에 따라 입력 내용을 수정할 수 있도록 합니다.

사례 5

질문 길이 제한

글자 제한:

  • API 유효성 검사:API 내 설문조사 질문 및 옵션에 대한 최대 글자 수 제한을 설정하세요. 이는 생성 및 업데이트 과정에서 질문의 길이와 각 옵션을 확인하여 수행할 수 있습니다.
  • 사용자 인터페이스 피드백:클라이언트측 검증을 구현하여 사용자가 입력하는 동안 글자 수 제한을 초과하면 즉각적인 피드백을 제공하여 사용자 경험을 향상시킵니다.

사례 6

설문조사 만료

만료 메커니즘:

  • 타임스탬프 관리:각 설문조사가 생성된 시기를 기록하기 위해 설문조사 테이블에 타임스탬프 필드를 추가하고 선택적으로 만료 날짜에 대한 다른 필드를 추가합니다.
  • 예약된 확인:만료된 폴링을 주기적으로 확인하고 데이터베이스에서 비활성으로 표시하는 백그라운드 작업 또는 크론 작업을 구현합니다. 여기에는 만료된 투표에 대한 투표를 방지하는 것도 포함될 수 있습니다.
  • 사용자 알림:선택적으로 설문조사 작성자와 참가자에게 만료 날짜가 임박했음을 알리고 설문조사가 비활성화되기 전에 참여할 수 있도록 합니다.

다음 글을 먼저 참고해주세요.

  1. 저수준 디자인: 폴링 시스템: 기본

  2. 하위 수준 설계: 폴링 시스템 - Node.js 및 SQL 사용

자세한 내용:

시스템 설계 관련 모든 기사 보기
Hastag: SystemDesignWithZeeshanAli

zeeshanali와 함께하는 시스템 디자인

Git: https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli

The above is the detailed content of Low-Level Design: Polling System - Edge Cases. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!