Еволюція архітектури 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
| Інструмент | Призначення | Рекомендація |
| 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)
Почніть з простого, але плануйте масштабування. Ваша команда подякує вам через рік!
Якщо стаття була корисною — поділіться! Маєте питання або досвід з подібними проєктами? Пишіть в коментарях!

9 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів