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

低レベル設計: ポーリング システム - Nodejs の使用

WBOY
WBOYオリジナル
2024-08-31 13:03:07380ブラウズ

Low-Level Design: Polling System - Using Nodejs

目次

  1. データベースのセットアップ
    • MySQL データベース スキーマ
    • ポーリング システムの ERD
  2. バックエンドのセットアップ
    • ステップ 1: プロジェクトを初期化する
    • ステップ 2: プロジェクトの構造
  3. APIの実装
    • ステップ 1: データベース接続 (db/db.js)
    • ステップ 2: 環境変数 (.env)
    • ステップ 3: コントローラーをポーリング (controllers/pollController.js)
    • ステップ 4: ルートをポーリングする (routes/pollRoutes.js)
    • ステップ 5: サーバー エントリ ポイント (index.js)
  4. エラー処理
  5. テスト
  6. 結論

「ポーリング システムの基本的な低レベル設計 - I

」の記事を参照してください。

データベースのセットアップ、Node.js と Express を使用した API 実装、MySQL との対話など、プロセス全体を詳細な手順に分けてみましょう。以下について説明します:

データベースのセットアップ

まず、MySQL データベースのスキーマを定義し、必要なテーブルを作成します。

MySQL データベース スキーマ

CREATE DATABASE polling_system;

USE polling_system;

CREATE TABLE polls (
    poll_id INT AUTO_INCREMENT PRIMARY KEY,
    question VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

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
);
  • 投票テーブル: 一意の識別子、質問、作成タイムスタンプを含む投票情報を保存します。

  • オプション テーブル: 投票に関連付けられたオプションを格納し、poll_id を介してリンクされます。

  • 投票テーブル: 各投票を記録し、投票、オプション、ユーザーにリンクします。

    ポーリングシステム用ERD

エンティティ:

  1. Polls: 投票自体を、poll_id や question などの属性で表します。
  2. オプション: 各投票で利用可能なオプションを、option_id や option_text などの属性で表します。
  3. 投票: ユーザーが投じた投票を、vote_id、user_id、タイムスタンプなどの属性で表します。

関係:

  1. 投票とオプションの間の 1 対多: 各投票には複数のオプションを含めることができます。
  2. 投票とオプション間の多対 1: 各投票は 1 つのオプションに関連付けられます。
  3. 投票と投票の間の多対 1: 各投票は特定の投票にリンクされています。

ERD の説明は次のとおりです:

  1. 投票表:

    • poll_id (主キー)
    • 質問
    • 作成場所
  2. オプション テーブル:

    • option_id (主キー)
    • poll_id (polls.poll_id を参照する外部キー)
    • オプションテキスト
  3. 投票テーブル:

    • vote_id (主キー)
    • poll_id (polls.poll_id を参照する外部キー)
    • option_id (options.option_id を参照する外部キー)
    • user_id
    • 作成場所

関係はエンティティ間の線で表されます:

  • 投票オプション: 1 つの投票に多くのオプションを含めることができます。
  • オプション投票: 1 つのオプションに複数の投票を含めることができます。
  • 投票投票: 1 つの投票に複数の投票を含めることができます。

バックエンドのセットアップ

Express と MySQL を使用して Node.js プロジェクトをセットアップしましょう。

ステップ 1: プロジェクトを初期化する

mkdir polling-system
cd polling-system
npm init -y
npm install express mysql2 dotenv

  • express: Node.js の Web フレームワーク。
  • mysql2: Node.js 用の MySQL クライアント。
  • dotenv: 環境変数の管理用。

ステップ 2: プロジェクトの構造

プロジェクトの基本構造を作成します:

polling-system/
│
├── .env
├── index.js
├── db/
│   └── db.js
├── routes/
│   └── pollRoutes.js
└── controllers/
    └── pollController.js

APIの実装

ステップ 1: データベース接続

ファイル - db/db.js

const mysql = require('mysql2/promise');
require('dotenv').config();

const pool = mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
});

module.exports = pool;

ステップ 2: 環境変数

ファイル - .env

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=polling_system
PORT=3000

ステップ 3: コントローラーをポーリングする

ファイル -controllers/pollController.js

このファイルは、ポーリング システムに必要なすべての CRUD 操作を実装します。

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 (question) VALUES (?)',
            [question]
        );

        const pollId = result.insertId;

        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 || !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 [pollResult] = await connection.execute(
            'UPDATE polls SET question = ? WHERE poll_id = ?',
            [question, pollId]
        );

        if (pollResult.affectedRows === 0) {
            await connection.rollback();
            connection.release();
            return res.status(404).json({ message: "Poll not found." });
        }

        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);
        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." });
    }
};

ステップ 4: ポーリング ルート

ファイル - Routes/pollRoutes.js
各 API エンドポイントのルートを定義します:

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;

ステップ 5: サーバーのエントリ ポイント

ファイル -index.js
最後に、サーバーをセットアップします:

const express = require('express');
const pollRoutes = require('./routes/pollRoutes');
require('dotenv').config();

const app = express();
app.use(express.json());

// Routes
app.use('/api', pollRoutes);

// Error Handling Middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ message: "Internal server error" });
});

// Start Server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

エラー処理

各メソッドには、無効な入力、重複投票、投票やオプションの欠落、サーバー エラーなどの一般的な問題に対するエラー処理が含まれています。

  • 入力検証: 必須フィールドが存在し、適切にフォーマットされているかどうかなど、入力が有効であることを確認するためのチェックが実行されます。
  • トランザクション管理: 複数のクエリを伴う操作 (ポーリングの作成または更新など) では、一貫性を確保するためにトランザクションが使用されます。

テスト

Postman やcurl などのツールを使用して各エンドポイントをテストします。

  • 投票の作成: 質問とオプションの配列を含む JSON 本文を使用して /api/polls を POST します。
  • 投票の更新: 更新された質問とオプションを含む /api/polls/:pollId を PUT します。
  • 投票の削除: DELETE /api/polls/:pollId.
  • 投票で投票: userId とオプションを使用して /api/polls/:pollId/vote を POST します。
  • 投票結果の表示: GET /api/polls/:pollId/results.

結論

これは、Node.js、Express、MySQL を使用したオンライン投票システムの包括的なモジュール実装です。基本的な CRUD 操作を処理し、トランザクションとのデータの一貫性を保証します。 API をより堅牢で使いやすくするための基本的なエラー処理も含まれています。

「ポーリング システムの基本的な低レベル設計 - I

」の記事を参照してください。

詳細:

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

zeeshanali とのシステムデザイン

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

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

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