Skip to content

Зразок архітектури сервера на Node.js

Posted on:April 11, 2025 at 03:57 PM

Архітектура однопоточного сервера з готовністю до масштабування

Концепція та принципи дизайну

Ця архітектура базується на принципах, що забезпечують легкий перехід від однопоточної до багатопоточної обробки:

  1. Абстракція процесів обробки - відділення логіки обробки від механізму її виконання
  2. Асинхронність операцій - використання неблокуючих операцій навіть в однопоточному режимі
  3. Статлес-дизайн - мінімізація стану, що зберігається в пам’яті одного процесу
  4. Шаблон публікації/підписки - використання системи подій для комунікації між компонентами

Структура однопоточного сервера

Ключові компоненти

/server
  server.js                 // Головний файл сервера
  /core
    task-processor.js       // Процесор завдань (абстракція виконавця)
    event-bus.js            // Внутрішня шина подій
    processing-manager.js   // Менеджер обробки завдань
  /services
    /openai
    /wordpress
    /google-sheets
    /data
      database.service.js   // Сервіс доступу до бази даних
      template.service.js   // Сервіс роботи з шаблонами промптів
  /api
    /routes
    /controllers
    /middlewares
  /utils
    result-store.js         // Сховище результатів (абстракція)
    progress-tracker.js     // Відстеження прогресу
  /models                   // Моделі даних для роботи з БД
    prompt-template.model.js // Модель шаблону промптів
    processing-task.model.js // Модель завдання на обробку
    api-key.model.js        // Модель для зберігання API ключів
    generation-result.model.js // Модель результату генерації

Дизайн компонентів для масштабування

1. Абстракція процесора завдань

// task-processor.js - концептуальний код
class TaskProcessor {
  constructor() {
    this.processing = false
    this.taskQueue = []
  }

  async addTask(task) {
    this.taskQueue.push(task)
    if (!this.processing) {
      this.processQueue()
    }
    return task.id // Повертаємо ID для відстеження
  }

  async processQueue() {
    this.processing = true
    while (this.taskQueue.length > 0) {
      const task = this.taskQueue.shift()
      try {
        await this.executeTask(task)
      } catch (error) {
        EventBus.emit("task:error", { taskId: task.id, error })
      }
    }
    this.processing = false
  }

  async executeTask(task) {
    // В однопоточній версії - пряме виконання завдання
    // При багатопоточності - відправка в чергу завдань
    return await task.execute()
  }
}

2. Абстракція сховища результатів

// result-store.js - концептуальний код
class ResultStore {
  constructor() {
    this.results = new Map()
    // В багатопоточній версії буде використовуватись Redis/MongoDB
  }

  async saveResult(taskId, result) {
    this.results.set(taskId, result)
    // В однопоточній версії зберігаємо в пам'яті
    // У багатопоточній - у зовнішньому сховищі
    EventBus.emit("result:saved", { taskId, status: "completed" })
  }

  async getResult(taskId) {
    return this.results.get(taskId)
    // У багатопоточній версії - читання з зовнішнього сховища
  }
}

3. Система подій для координації

// event-bus.js - концептуальний код
class EventBus {
  static listeners = {}

  static on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = []
    }
    this.listeners[event].push(callback)
  }

  static emit(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach((callback) => callback(data))
    }
  }
}

4. Абстракція доступу до бази даних

// database.service.js - концептуальний код
class DatabaseService {
  constructor(config) {
    this.connection = null
    this.config = config
  }

  async connect() {
    // В однопоточній версії - проста локальна БД, SQLite або локальний MongoDB
    // У багатопоточній - підключення до зовнішньої MongoDB/PostgreSQL
    // Реалізація залежить від вибору БД
  }

  async findOne(collection, query) {
    // Універсальний метод пошуку, який може працювати з різними БД
  }

  async find(collection, query, options) {
    // Універсальний метод пошуку колекції документів
  }

  async insert(collection, data) {
    // Універсальний метод вставки даних
  }

  async update(collection, query, data) {
    // Універсальний метод оновлення даних
  }

  async delete(collection, query) {
    // Універсальний метод видалення даних
  }
}

5. Сервіс шаблонів промптів

// template.service.js - концептуальний код
class TemplateService {
  constructor(databaseService) {
    this.db = databaseService
  }

  async getTemplateById(id) {
    // Отримання шаблону промпту за ID
    return this.db.findOne("promptTemplates", { id })
  }

  async getTemplateByType(type) {
    // Отримання шаблону за типом (стаття, QA, коментарі)
    return this.db.findOne("promptTemplates", { type })
  }

  async saveTemplate(template) {
    // Збереження нового шаблону
    return this.db.insert("promptTemplates", template)
  }

  async updateTemplate(id, template) {
    // Оновлення існуючого шаблону
    return this.db.update("promptTemplates", { id }, template)
  }

  async compileTemplate(templateId, variables) {
    // Компіляція шаблону з підстановкою змінних
    const template = await this.getTemplateById(templateId)
    if (!template) throw new Error("Template not found")

    return Object.entries(variables).reduce(
      (text, [key, value]) =>
        text.replace(new RegExp(`{{${key}}}`, "g"), value),
      template.content
    )
  }
}

Моделі даних

Модель шаблону промптів

// prompt-template.model.js - концептуальний код
class PromptTemplate {
  constructor(data) {
    this.id = data.id || generateUuid()
    this.name = data.name
    this.type = data.type // 'article', 'qa', 'comment', etc.
    this.content = data.content
    this.variables = data.variables || []
    this.createdAt = data.createdAt || new Date()
    this.updatedAt = data.updatedAt || new Date()
  }

  toJSON() {
    return {
      id: this.id,
      name: this.name,
      type: this.type,
      content: this.content,
      variables: this.variables,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
    }
  }
}

Модель завдання на обробку

// processing-task.model.js - концептуальний код
class ProcessingTask {
  constructor(data) {
    this.id = data.id || generateUuid()
    this.type = data.type // 'generate_article', 'generate_qa', etc.
    this.sourceType = data.sourceType // 'google_sheet', 'direct_input'
    this.sourceData = data.sourceData // URL or direct data
    this.templateId = data.templateId
    this.options = data.options || {}
    this.status = data.status || "pending" // 'pending', 'processing', 'completed', 'failed'
    this.progress = data.progress || 0
    this.result = data.result || null
    this.error = data.error || null
    this.createdAt = data.createdAt || new Date()
    this.updatedAt = data.updatedAt || new Date()
  }

  updateStatus(status, options = {}) {
    this.status = status
    if (options.progress) this.progress = options.progress
    if (options.result) this.result = options.result
    if (options.error) this.error = options.error
    this.updatedAt = new Date()
  }

  toJSON() {
    return {
      id: this.id,
      type: this.type,
      sourceType: this.sourceType,
      sourceData: this.sourceData,
      templateId: this.templateId,
      options: this.options,
      status: this.status,
      progress: this.progress,
      result: this.result,
      error: this.error,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
    }
  }
}

Процес обробки в однопоточній архітектурі

  1. Запит від клієнта → API контролер приймає запит
  2. Створення завдання → Контролер формує об’єкт завдання
  3. Планування виконання → Завдання додається до TaskProcessor
  4. Повернення ID → Клієнт отримує ID завдання для відстеження
  5. Асинхронне виконання → TaskProcessor виконує завдання у фоні
  6. Збереження результату → Результат зберігається в ResultStore
  7. Сповіщення про завершення → EventBus сповіщає про завершення
  8. Запит результату → Клієнт отримує результат за ID

Діаграма потоку даних для однопоточної версії

+-------------+     +-------------------+     +-----------------+
| Client      |     | Server (Single    |     | External APIs   |
|             |     | Thread Process)   |     |                 |
+-------------+     +-------------------+     +-----------------+
      |                      |                        |
      | 1. API Request       |                        |
      |--------------------->|                        |
      |                      |                        |
      |                      |--+                     |
      |                      |  | 2. Create Task      |
      |                      |<-+ Object              |
      |                      |                        |
      |                      |--+                     |
      |                      |  | 3. Add Task to      |
      |                      |<-+ Internal Queue      |
      |                      |                        |
      | 4. Return Task ID    |                        |
      |<---------------------|                        |
      |                      |                        |
      |                      |--+                     |
      |                      |  | 5. Process Task     |
      |                      |<-+ Asynchronously      |
      |                      |                        |
      |                      | 6. API Call            |
      |                      |----------------------->|
      |                      |                        |
      |                      | 7. API Response        |
      |                      |<-----------------------|
      |                      |                        |
      |                      |--+                     |
      |                      |  | 8. Save Results     |
      |                      |<-+ to Memory Store     |
      |                      |                        |
      |                      |--+                     |
      |                      |  | 9. Emit             |
      |                      |<-+ Completion Event    |
      |                      |                        |
      | 10. Status Request   |                        |
      |--------------------->|                        |
      |                      |                        |
      | 11. Return Results   |                        |
      |<---------------------|                        |

Готовність до масштабування

Що залишається незмінним при переході до багатопоточності

  1. API інтерфейс - всі ендпоінти залишаються незмінними
  2. Формат завдань - структура об’єктів завдань зберігається
  3. Система подій - принцип комунікації через події
  4. Бізнес-логіка - вся логіка сервісів залишається такою ж

Що змінюється при масштабуванні

  1. TaskProcessor:

    • Замість прямого виконання - відправка в чергу завдань
    • Інтеграція з Bull/Bee-Queue для розподілу завдань
  2. ResultStore:

    • Перехід від зберігання в пам’яті до Redis/MongoDB
    • Додавання TTL для результатів
  3. EventBus:

    • Заміна на Redis Pub/Sub або подібне рішення
    • Додавання підтримки кластерних подій
  4. Сервер:

    • Запуск кількох воркерів через PM2/Docker
    • Додавання балансувальника навантаження

Діаграма потоку даних для багатопоточної версії

+-----------+    +-------------+    +-----------------+
| API Layer |    | Task Queue  |    | Worker Process  |
+-----------+    +-------------+    +-----------------+
      |                 |                    |
      | Create Task     |                    |
      |---------------->|                    |
      |                 |                    |
      | Return Task ID  |                    |
      |<----------------|                    |
      |                 |                    |
      |                 | Process Task       |
      |                 |------------------->|
      |                 |                    |
      |                 |                    | Save Result
      |                 |<-------------------|
      |                 |                    |
      | Get Result      |                    |
      |---------------->|                    |
      |                 |                    |
      | Return Result   |                    |
      |<----------------|                    |

Стратегія впровадження масштабування

Фаза 1: Однопоточне виконання

Фаза 2: Підготовка до масштабування

Фаза 3: Повне масштабування

Технічні деталі реалізації

Однопоточна версія

Багатопоточна версія

Висновки

Запропонована архітектура дозволяє почати з простого однопоточного рішення, але має всі необхідні абстракції для легкого переходу до багатопоточної обробки без значного рефакторингу коду. Ключовим є чітке відділення обробки завдань від API та використання системи подій для комунікації між компонентами.

Процес отримання та обробки даних

Джерела промптів та даних

  1. Шаблони промптів:

    • Зберігаються в базі даних
    • Містять змінні частини для підстановки
    • Можуть оновлюватись адміністратором через API
  2. Вхідні дані для генерації:

    • Google Sheets:
      • URL таблиці передається з фронтенда
      • Сервер отримує доступ та читає дані
      • Обробляє рядки згідно з шаблоном
    • Пряме введення:
      • Дані передаються безпосередньо з фронтенда
      • Використовуються для одноразової генерації
  3. Конфігурації генерації:

    • Параметри генерації для OpenAI API
    • Налаштування форматування результатів
    • Збережені у БД з можливістю вибору при генерації

Потік роботи з даними

  1. Запит від клієнта:
{
  "type": "generate_article",
  "sourceType": "google_sheet",
  "sourceData": "https://docs.google.com/spreadsheets/d/...",
  "templateId": "article_template_1",
  "options": {
    "model": "gpt-4",
    "maxTokens": 4000,
    "temperature": 0.7
  }
}
  1. Підготовка даних на сервері:

    • Отримання шаблону промпту з БД
    • Отримання даних з Google Sheets
    • Валідація даних і перевірка необхідних колонок
  2. Створення завдання:

    • Формування моделі ProcessingTask
    • Збереження в базі даних для відстеження
    • Повернення ID завдання клієнту
  3. Обробка даних:

    • Для кожного рядка з таблиці:
      • Підстановка даних у шаблон промпту
      • Запит до OpenAI API
      • Обробка результатів
      • Публікація у WordPress (за потреби)
      • Оновлення прогресу у базі даних
  4. Збереження результатів:

    • Повні результати зберігаються в БД
    • Оновлення статусу завдання
    • Сповіщення клієнта про завершення

Діаграма потоку даних (розширена)

+-------------+      +---------------+      +----------------+
| Client      |      | API Server    |      | Database       |
+-------------+      +---------------+      +----------------+
      |                     |                       |
      | Request Task        |                       |
      | (template, data)    |                       |
      |-------------------->|                       |
      |                     | Get Template          |
      |                     |---------------------->|
      |                     |                       |
      |                     | Template Data         |
      |                     |<----------------------|
      |                     |                       |
      |                     | Create Task           |
      |                     |---------------------->|
      |                     |                       |
      | Task ID             |                       |
      |<--------------------|                       |
      |                     |                       |
      |                     | Process Data          |
      |                     |--------+              |
      |                     |        |              |
      |                     |        v              |
      |                     | +------------+        |
      |                     | | Google     |        |
      |                     | | Sheets API |        |
      |                     | +------------+        |
      |                     |        |              |
      |                     |<-------+              |
      |                     |                       |
      |                     | +------------+        |
      |                     | | OpenAI API |        |
      |                     | +------------+        |
      |                     |        |              |
      |                     |<-------+              |
      |                     |                       |
      |                     | +------------+        |
      |                     | | WordPress  |        |
      |                     | | API        |        |
      |                     | +------------+        |
      |                     |        |              |
      |                     |<-------+              |
      |                     |                       |
      |                     | Update Task Status    |
      |                     |---------------------->|
      |                     |                       |
      | Get Task Status     |                       |
      |-------------------->|                       |
      |                     | Get Task              |
      |                     |---------------------->|
      |                     |                       |
      |                     | Task Data             |
      |                     |<----------------------|
      |                     |                       |
      | Task Result         |                       |
      |<--------------------|                       |
      |                     |                       |

Стратегія розвитку бази даних при масштабуванні

Фаза 1: Мінімальна база даних (однопоточне виконання)

Фаза 2: Централізована база даних

Фаза 3: Розподілена база даних

Висновки

Запропонована архітектура включає всі необхідні компоненти для повноцінної роботи з даними та базою даних:

  1. Абстрактний доступ до БД - єдиний інтерфейс, незалежно від типу бази даних
  2. Система моделей - чітке представлення даних у системі
  3. Шаблони промптів - гнучке налаштування логіки генерації
  4. Джерела даних - підтримка як Google Sheets, так і прямого введення

Ця архітектура дозволяє починати з простої однопоточної системи з мінімальними вимогами до інфраструктури, але має всі необхідні компоненти для легкого масштабування при зростанні навантаження.