Еволюція архітектури IoT проєктів: від хаосу до монорепо

Як правильно організувати базу даних в IoT-системі з адмін-панеллю та бекенд-сервісами

Вступ

Кожен розробник IoT-систем рано чи пізно стикається з дилемою: як організувати архітектуру проєкту, щоб адмін-панель та бекенд не конфліктували через базу даних? У цій статті розглянемо еволюцію підходів від найгіршого до найкращого на прикладі реального проєкту.

Контекст проєкту

Маємо типову IoT-систему:

  • IoT Backend — Express.js сервер з MQTT брокером
  • Admin Panel — Next.js додаток для управління пристроями
  • PostgreSQL — база даних
  • Prisma — ORM для роботи з БД

Завдання: організувати архітектуру так, щоб обидва сервіси працювали з однією базою даних без конфліктів.

❌ Підхід 1: «Бекенд керує всім» (Антипатерн)

Як виглядає:

services:
  backend:
    environment:
      - SETUP_DATABASE=true  # Backend налаштовує схему
  admin-panel:
    depends_on:
      - backend  # Чекає коли backend налаштує БД

Проблеми:

  • 🚫 Бекенд виходить за межі своїх обов’язків
  • 🚫 Адмін-панель залежить від бекенда
  • 🚫 Незрозуміло, хто відповідає за схему БД
  • 🚫 Складно масштабувати

Чому так не можна:

Бекенд повинен використовувати дані, а не керувати схемою. Це порушує принцип Single Responsibility.

🤔 Підхід 2: «Адмін-панель керує схемою» (Краще, але не ідеально)

Як виглядає:

services:
  admin-panel:
    environment:
       - SETUP_DATABASE=true # Admin панель налаштовує схему
  backend:
     depends_on:
         - admin-panel # Чекає готову схему
     environment:
         - WAIT_FOR_SCHEMA=true

Переваги:

  • ✅ Логічний розподіл: адмін керує, бекенд використовує
  • ✅ Бекенд не займається налаштуванням БД
  • ✅ Чітка послідовність запуску

Недоліки:

  • 🤔 Дублювання Prisma схеми в обох проєктах
  • 🤔 Синхронізація змін вручну
  • 🤔 Різні версії типів TypeScript

✅ Підхід 3: «Shared Database Package» (Ідеальний)

Архітектура:

monorepo/
├── packages/
│  └── database/ # 🎯 Централізована схема
│      ├── prisma/schema.prisma
│      ├── generated/prisma/
│      └── src/client.ts
├── admin-panel/ # Використовує database package
├── backend/ # Використовує database package
└── pnpm-workspace.yaml

Як працює:

1. Database Package


// packages/database/src/client.ts
import { PrismaClient } from '../generated/prisma'
 
export const prisma = new PrismaClient({
  log: ['query', 'error', 'warn']
})
 

2. Admin Panel


// admin-panel/src/lib/prisma.ts
import { prisma } from 'database'  // ✅ Імпорт з shared package
export { prisma }
 

3. Backend


// backend/src/database.js
const { prisma } = require('database')  // ✅ Той самий клієнт
module.exports = { prisma }
 

Переваги:

  • Single Source of Truth — одна схема для всіх
  • Автоматична синхронізація типів
  • Type Safety у всьому проєкті
  • Легке тестування — одна база для всіх
  • Простота розгортання — зрозуміла послідовність

🔧 Імплементація кроками

Крок 1: Створюємо Database Package


// packages/database/package.json
{
  "name": "database",
  "scripts": {
    "db:generate": "prisma generate",
    "db:deploy": "prisma migrate deploy",
    "db:studio": "prisma studio"
  },
  "dependencies": {
    "@prisma/client": "^5.16.0"
  }
}
 

Крок 2: Налаштовуємо Workspace


# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'admin-panel'
  - 'backend'
 

Крок 3: Оновлюємо залежності


// admin-panel/package.json & backend/package.json
{
  "dependencies": {
    "database": "workspace:*"
  }
}

Крок 4: Docker для Monorepo

 # docker-compose.yml
services:
  admin-panel:
    build:
      context: . # Build з root для доступу до packages/
    volumes:
      - ./packages/database:/app/packages/database
  backend:
    build:
      context: . # Той самий контекст
    depends_on:
      - admin-panel # Чекає схему

📊 Порівняння підходів

КритерійBackend керуєAdmin керуєShared Package
Логічність❌ Погано✅ Добре✅ Відмінно
Синхронізація❌ Ручна❌ Ручна✅ Автоматична
Type Safety❌ Конфлікти🤔 Ручна✅ Автоматична
Масштабованість❌ Складно🤔 Середньо✅ Легко
Maintenance❌ Високий🤔 Середній✅ Низький

🚀 Практичні переваги Monorepo

1. Розробка

# Одна команда для всього
pnpm dev
# Зміна схеми автоматично оновлює типи скрізь
vim packages/database/prisma/schema.prisma
pnpm db:generate # Оновлює всі проєкти

2. CI/CD

# .github/workflows/ci.yml
- name: Setup database
  run: pnpm --filter database db:generate
- name: Test all projects
  run: pnpm test # Тестує з консистентною схемою

3. Команда

  • Новий розробник бачить всю архітектуру в одному репо
  • Легко робити cross-service зміни
  • Відсутність «mystery dependencies»

⚠️ Поширені помилки

1. «Скопіюю схему швидко»

# ❌ НЕ РОБІТЬ ТАК
cp admin-panel/prisma/schema.prisma backend/prisma/

Проблема: Через тиждень схеми стануть різними.

2. «Буду синхронізувати вручну»

Проблема: Людський фактор + складність проєкту = гарантовані помилки.

3. «Monorepo це складно»

Реальність: Сучасні інструменти (pnpm, Turborepo, Nx) роблять це простим.

🎯 Висновки

Коли використовувати Shared Database Package:

  • ✅ Кілька сервісів працюють з однією БД
  • ✅ Потрібна консистентність типів
  • ✅ Команда більше 2 розробників
  • ✅ Планується розвиток проєкту

Коли можна обійтися простішим:

  • 🤔 Маленький проєкт (1 розробник)
  • 🤔 Схема БД не змінюється
  • 🤔 Різні бази для різних сервісів

🛠️ Інструменти для monorepo

ІнструментПризначенняРекомендація
pnpmPackage manager⭐⭐⭐⭐⭐
TurborepoBuild orchestration⭐⭐⭐⭐⭐
NxMonorepo framework⭐⭐⭐⭐
LernaLegacy tool⭐⭐

Майбутнє

Тренди в архітектурі IoT-проєктів:

  • Micro-frontends для адмін-панелей
  • Event-driven architecture для real-time даних
  • Edge computing для обробки IoT-даних
  • GraphQL Federation для великих систем

Заключення

Правильна архітектура — це не «показуха», а інвестиція в майбутнє проєкту. Shared Database Package підхід дозволяє:
— Зосередитися на бізнес-логіці, а не на синхронізації

— Швидше додавати нові фічі

— Уникати помилок через inconsistency
— Покращити DX (Developer Experience)

Почніть з простого, але плануйте масштабування. Ваша команда подякує вам через рік!

Якщо стаття була корисною — поділіться! Маєте питання або досвід з подібними проєктами? Пишіть в коментарях!

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Я вам рекомендую не використовувати LLM для написання текстів, бо це не можливо сприймати всерйоз без редактури

Я бачив як за дизайн док приблизно такого ж рівня як ця стаття людей звільняли.

дякую за коментар, уточніть будь ласка про який дизайн йде мова? Дизайн документації як все структуровано? Дизайн архітектури системи?
Я радий проявленому інтересу та за конкретну критику це дозволить висвітлити рішення більш детально.

Кожен розробник IoT-систем рано чи пізно стикається з дилемою: як організувати архітектуру проєкту, щоб адмін-панель та бекенд не конфліктували через базу даних?

Поясніть проблему краще, може для ІоТ це щось типове, але поки не ясно:
— що таке бекенд, які його задачі?
— що таке адмін-панель, яка її область відповідальності?

2 сервіси, що ходять в одну БД — це відомий антипатерн. Він іноді може бути виправданим, але ви не навели пояснень чому це необхідно в вашому випадку. Чому це потрібно?

Теоретично з відповідей на питання вище можливо сприйняття зміниться, але поки:
Маємо проект з про..баною архітектурою навіть на рівні розбиття по домену (по сутностям бізнес логіки).
Типово описана проблема вирішується просто: Є сервіс, що логічно володіє/інкапсулює в собі певні дані. Він має і володіти схемою, і надавати дані іншим сервісам по АПІ (не обов’язково РЕСТ).
В вашому випадку можливо у бекенду та адмін панелі будуть свої БД, бо це різні домени.

Тактична порада. Типове рішення, що саме міграцією БД має займатись джоба, що виконується при деплої. У вас дуже схоже залишився «шорткат» де один з серсівіс мігрує БД. Навіть таке тактичне рішення дозволить застосувати «підхід 1» і не мати залежності між сервісами для старту.

Богдан, дякую за детальну критику. Ви праві щодо антипатерну у мікросервісних архітектурах.
Та варто прояснити, у моєму IoT проекті зовсім інша ситуація.
Моя система — монолітна архітектура:
— Express Backend керує всіма операціями: MQTT комунікація, оброблення команд, запис у БД, heartbeat моніторинг пристроїв
— Next.js Admin Panel — інтерфейс користувача для цього backend’а
— Admin Panel передає команди backend’у через REST API, не пише напряму у БД
-Спільна Prisma схема служить Single Source of Truth для обох частин

Чому я думаю, що Shared Database логічна тут:

Backend — це один домен, який керує всіма операціями системи.
Admin Panel — інтерфейс для цього домену. Вони не конфліктують, тому що backend контролює всі операції з БД. Admin Panel лише читає дані та передає команди backend’у.

Як це працює:
— Користувач натискає кнопку в Admin Panel
— Admin Panel відправляє HTTP запит до backend’а
— Backend обробляє команду, пише дані в БД
— Socket.IO повідомляє Admin Panel про результат
— UI оновлюється в реальному часі

Якби у мене було 10 незалежних мікросервісів (Orders Service, Users Service, Payment Service), кожен зі своєю областю відповідальності — тоді ви були б абсолютно праві.
Кожен мікросервіс повинен мати свою БД та спілкуватися через API.
Але поточна архітектура монолітна, тому Shared Database Package — на мою думку правильне рішення, не антипатерн.
Дуже дякую за критику — вона допомогла мені чітше сформулювати контекст архітектури.

P.S. Детальніше про архітектуру цього IoT проекту я описав в топіку «IoT PowerHub: Як я створив промислову IoT систему з нуля» (на модерації). Там показую як все працює на практиці: від залізяки до backend’а та UI. Дякую, що проявили інтерес та залишили ваші відгуки

Як це працює:
— Користувач натискає кнопку в Admin Panel
— Admin Panel відправляє HTTP запит до backend’а
— Backend обробляє команду, пише дані в БД
— Socket.IO повідомляє Admin Panel про результат
— UI оновлюється в реальному часі

Тут ні слова про те щоб панель працювала з БД. Для чого їй тоді взагалі схема чи «ентіті» створені призмою? Панель має працювати з моделями/дто рівня АПІ і не має знати про існування навіть БД і тим паче про призва моделі.

-Спільна Prisma схема служить Single Source of Truth для обох частин

«Єдине джерело правди» — це певні дані в певному сховищі. Призма не має (і не факт що може) містити даних.

Знову ж здогадка:
Ви під цим терміном маєте на увазі «опис моделей даних, що використовуються в різних сервісах». Якщо так, то:
1) Для цього є умовний ОпенАпі. Якщо це заважке рішення, то можна зробити шаред бібліотеку з ДТОшками. Дуже схоже, що ви це і зробили, але тоді є 2.
2) Використовувати моделі ОРМ (Призми) як контракт між сервісами — погана ідея, бо не дає можливість мігрувати схему і тим паче спосіб збереження даних (умовно технологію БД) без зміни клієнтських сервісів. Такий підхід — це фактично розподілений моноліт.

вебморда та бекенд — це тільки частина (можлива) якого-небудь iot проекту якщо брати в цілому

Звісно, але інші топіки на модерації поки що. Зокрема описав цей проект в топіку — IoT PowerHub: Як я створив промислову IoT систему з нуля

Підписатись на коментарі