ホームページ  >  記事  >  ウェブフロントエンド  >  低レベルの設計: ポーリング システム - エッジ ケース

低レベルの設計: ポーリング システム - エッジ ケース

WBOY
WBOYオリジナル
2024-08-31 14:43:33516ブラウズ

Low-Level Design: Polling System - Edge Cases

目次

ケース 1 - 更新のためのバージョニングの処理
ケース 2 - PollID を主キーではなく UUID として使用する
ケース 3 - 空または無効なオプション
ケース 4 - オプションの重複
ケース 5 - 質問の長さの制限
ケース 6 - 投票の有効期限

まず次の記事を参照してください:

  1. 低レベル設計: ポーリング システム: 基本

  2. 低レベル設計: ポーリング システム - Node.js と SQL の使用

エッジケースの処理

ケース1

同じ投票 ID に関連付けられた以前の詳細を保持しながら、投票の質問とオプションの更新を管理するには、バージョン管理システムを実装できます。このアプローチにより、各投票の履歴データを追跡し、更新後も古い詳細が確実に保持されるようにすることができます。

ステップ 1: データベース スキーマの変更

  1. 投票表を更新します

    • 最新バージョンの投票を追跡するには、current_version_id 列を投票テーブルに追加します。
  2. ポーリングバージョンテーブルを作成します

    • 投票の履歴バージョンを保存する新しいテーブルを作成します。

更新されたデータベーススキーマ

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
);

ステップ 2: API 実装の変更

ポーリングコントローラーを更新する

新しいバージョンを作成する前に、質問が変更されたかどうかを確認するために updatePoll メソッドを変更します。

ファイル: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." });
    }
};

ステップ 3: ポーリング ルートを更新する

pollRoutes.js でルートが適切に定義されていることを確認してください。

ファイル: ルート/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;

変更の概要

  1. データベース:

    • current_version_id を含めるために投票テーブルを更新しました。
    • 質問のバージョンを追跡するために、poll_versions テーブルを作成しました。
    • オプションと投票テーブルは変更されません。
  2. API:

    • 投票とバージョンを初期化するための新しい createPoll メソッドを作成しました。
    • 新しいバージョンを作成する前に質問の変更をチェックするために updatePoll メソッドを更新しました。
    • 投票および投票結果の表示方法を追加しました。
  3. ルーティング:

    • 投票の作成、更新、投票、結果を処理するために必要なルートがすべて定義されていることを確認しました。

ケース2

pollId が UUID (Universally Unique Identifier) である必要があるシナリオを処理します。

コードを提供せずにポーリング システムに thepollId の UUID を実装する手順は次のとおりです。

ポーリング ID の UUID を実装する手順

  1. ** データベース スキーマの更新:**

    • polls、poll_versions、options、votes テーブルを変更して、poll_id に整数ではなく CHAR(36) を使用します。
    • UUID によってリンクされたアンケートの質問とオプションの履歴バージョンを保存する新しい poll_versions テーブルを作成します。
  2. ** UUID 生成:**

    • UUID を生成する方法を決定します。UUID を作成するには、アプリケーション環境のライブラリまたは組み込み関数を使用できます。
  3. ** ポーリング ロジックの作成:**

    • 新しい投票を作成するときは、UUID を生成し、それを poll_id として使用します。
    • 新しい投票レコードを投票テーブルに挿入します。
    • 最初の質問をpoll_versionsテーブルに挿入し、生成されたUUIDにリンクします。
  4. ** 投票ロジックの更新:**

    • アンケートを更新する場合:
  5. 質問が変更されたかどうかを確認してください。

    • 質問が変更された場合は、poll_versions テーブルに新しいバージョンのエントリを作成して、古い質問とオプションを保存します。
    • 必要に応じて、新しい質問とオプションを使用して投票表を更新します。
  6. ** 投票ロジック:**

    • 投票メカニズムを更新して、poll_id として UUID を使用するようにします。
  7. 投票リクエストで指定された UUID が投票テーブルに存在することを検証します。

  8. ** API アップデート:**

    • poll_id の UUID を受け入れて返すように API エンドポイントを変更します。
    • すべての API 操作 (作成、更新、削除、投票) が一貫して UUID 形式を参照するようにします。
  9. ** テスト:**

    • アプリケーションを徹底的にテストして、すべてのシナリオ (作成、更新、投票、投票結果の取得) で UUID が正しく処理されることを確認します。
  10. ** ドキュメント:**

    • poll_id 形式の変更と、バージョン管理と UUID の使用に関連する新しい動作を反映するために API ドキュメントを更新します。

これらの手順に従うことで、データの整合性と履歴追跡を確保しながら、ポーリング システムに pollId の UUID を正常に実装できます。


ケース3

空または無効なオプション

検証アプローチ:

  • API 入力検証: API エンドポイントにチェックを実装して、リクエスト本文で指定されたオプションが空ではなく、特定の基準を満たしていること (許可されていない場合は特殊文字がないことなど) を検証します。
  • フィードバック メカニズム: オプションが無効または空の場合は、明確なエラー メッセージをユーザーに提供し、入力を修正するようにガイドします。

ケース4

オプションの重複

一意性チェック:

  • 挿入前の検証: 投票にオプションを追加する前に、データベース内の既存のオプションに重複がないか確認してください。これは、ポーリング ID を使用してオプション テーブルをクエリし、新しいオプションと比較することで実行できます。
  • ユーザー フィードバック: 重複したオプションが検出された場合は、意味のあるエラー メッセージを返してユーザーにどのオプションが重複しているかを知らせ、それに応じて入力を変更できるようにします。

事例5

質問の長さ制限

文字制限:

  • API 検証: API 内のアンケートの質問とオプションの最大文字数制限を設定します。これは、作成および更新プロセス中に質問の長さと各オプションを確認することで実行できます。
  • ユーザー インターフェイスのフィードバック: クライアント側の検証を実装して、ユーザーが入力中に文字数制限を超えた場合に即座にフィードバックを提供し、ユーザー エクスペリエンスを向上させます。

事例6

投票期限

有効期限のメカニズム:

  • タイムスタンプ管理: 各投票がいつ作成されたかを記録するタイムスタンプ フィールドを投票テーブルに追加し、オプションで有効期限の別のフィールドを追加します。
  • スケジュールされたチェック: 期限切れのポーリングを定期的にチェックし、データベース内で非アクティブとしてマークするバックグラウンド ジョブまたは cron タスクを実装します。これには、期限切れの投票への投票を阻止することも含まれる場合があります。
  • ユーザー通知: オプションで、投票の作成者と参加者に有効期限が近づいていることを通知し、投票が非アクティブになる前に投票に参加できるようにします。

まず次の記事を参照してください:

  1. 低レベル設計: ポーリング システム: 基本

  2. 低レベル設計: ポーリング システム - Node.js と SQL の使用

詳細:

システム設計に関連するすべての記事を取得します
タグ: SystemDesignWithZeeshanAli

zeeshanali とのシステムデザイン

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

以上が低レベルの設計: ポーリング システム - エッジ ケースの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。