Еволюція архітектури 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. “Скопіюю схему швидко”

Bash

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

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

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

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

3. “Monorepo це складно”

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


🎯 Висновки

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

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

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

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

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

Інструмент Призначення Рекомендація
pnpm Package manager ⭐⭐⭐⭐⭐
Turborepo Build orchestration ⭐⭐⭐⭐⭐
Nx Monorepo framework ⭐⭐⭐⭐
Lerna Legacy tool ⭐⭐

🔮 Майбутнє

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

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

📚 Корисні ресурси


💡 Заключення

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

  • 🎯 Зосередитися на бізнес-логіці, а не на синхронізації
  • 🚀 Швидше додавати нові фічі
  • 🛡️ Уникати помилок через inconsistency
  • 👥 Покращити DX (Developer Experience)

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


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

 

Опубліковано в Iot

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *