Архитектура и данные

Как выбрать технологический стек для веб-продукта: не по моде, а по задачам бизнеса

Когда начинается разработка нового веб-продукта, один из первых технических вопросов обычно звучит так:

На чём будем делать?

И почти сразу появляются знакомые названия:

React
Vue
Next.js
Node.js
Python
Go
PostgreSQL
Redis
Docker
Kubernetes

Со стороны может показаться, что выбор технологического стека — это поиск самых современных, быстрых или популярных технологий.

На практике всё устроено иначе.

Хороший стек — не тот, в котором больше модных названий.

Хороший стек — тот, который позволяет конкретному продукту:

  • быстро разрабатываться;
  • выдерживать нужную нагрузку;
  • безопасно хранить данные;
  • нормально обновляться;
  • не требовать чрезмерно дорогой инфраструктуры;
  • оставаться понятным разработчикам через несколько лет;
  • масштабироваться тогда, когда это действительно понадобится.

Для одного продукта отличным решением будет React, Node.js и PostgreSQL.

Для другого — обычный серверный HTML без SPA.

Для третьего понадобится Go.

Для четвёртого разумнее выбрать Java.

А в пятом случае самая серьёзная архитектурная ошибка будет заключаться не в выборе языка, а в том, что разработчики слишком рано добавили Redis, Kafka, Kubernetes, Elasticsearch и восемь микросервисов.

Поэтому технологический стек правильнее выбирать не от технологий к продукту:

Возьмём Next.js
+
NestJS
+
Redis
+
Kafka
+
Kubernetes

↓

А теперь придумаем,
зачем всё это нужно

а наоборот:

Что должен делать продукт?
        ↓
Какие у него ограничения?
        ↓
Какие риски?
        ↓
Какая ожидается нагрузка?
        ↓
Как он будет развиваться?
        ↓
Какая команда его поддерживает?
        ↓
Какие технологии решают эти задачи
с минимальной ненужной сложностью?

Разберём этот подход подробнее.


Технологический стек начинается не с программирования

Представим два проекта.

Первый — корпоративный сайт компании с двадцатью информационными страницами, экспертными статьями и формой заявки.

Второй — SaaS-система, где есть:

регистрация;
организации;
проекты;
роли;
платные тарифы;
личные кабинеты;
файлы;
уведомления;
фоновые задачи;
API;
интеграции;
real-time;
аналитика.

Технически оба проекта являются веб-приложениями.

Но использовать для них одинаковую архитектуру только потому, что команда привыкла к одному стеку, странно.

Первому продукту особенно важны:

SEO;
скорость загрузки;
простая публикация контента;
низкая стоимость эксплуатации.

Второму:

целостность данных;
авторизация;
бизнес-правила;
очереди;
масштабируемость backend;
audit;
интеграции;
наблюдаемость.

Поэтому перед выбором framework мы сначала определяем свойства системы.


Какие вопросы нужно задать до выбора технологий

Хороший архитектурный разговор начинается примерно с таких вопросов.

Что является основным бизнес-процессом?

Не:

Нам нужен личный кабинет.

А:

Что пользователь делает внутри него?

Например:

создаёт проект
↓
прикрепляет файлы
↓
получает оценку
↓
согласовывает работу
↓
следит за этапами
↓
получает результат

Это намного важнее выбора React или Vue.


Сколько данных будет храниться?

Десять тысяч записей?

Десять миллионов?

Миллиард событий?

Какие данные:

транзакционные;
документы;
изображения;
видео;
телеметрия;
поисковый индекс;
аналитика?

От этого напрямую зависит storage-архитектура.


Насколько критична консистентность?

Если пользователь поменял цвет интерфейса, задержка синхронизации на несколько секунд не страшна.

Если он оплатил заказ, система не должна дважды списать деньги из-за повторного webhook.

Если два администратора одновременно меняют стоимость договора, может потребоваться optimistic locking.

Не все данные имеют одинаковую цену ошибки.


Должна ли система работать в real-time?

Требование:

Всё должно обновляться без перезагрузки.

само по себе ещё не означает необходимость WebSocket.

Для редких обновлений может хватить:

HTTP request
+
обычного refresh данных

Для чата, совместной работы или live-monitoring WebSocket уже может быть оправдан.


Требуется ли offline-режим?

Если приложение должно продолжать работать без сети, появляются совсем другие задачи:

локальное хранилище;
очередь изменений;
конфликты;
синхронизация;
merge;
восстановление.

Offline-first — это архитектурное требование, а не дополнительная галочка в интерфейсе.


Насколько важен SEO?

Публичный экспертный центр, каталог товаров или сервисные страницы должны хорошо читаться поисковыми роботами.

Внутренняя CRM за авторизацией практически не нуждается в SEO.

Поэтому одинаковая frontend-архитектура для них необязательна.


Какие интеграции понадобятся?

Например:

1С;
CRM;
платёжная система;
Telegram;
email;
телефония;
S3;
OAuth;
служба доставки;
внешнее API.

Интеграции могут оказаться сложнее основного интерфейса.

Если внешняя система нестабильна, появляются очереди и retry.

Если webhook может повторяться — нужна идемпотентность.

Если данные идут в обе стороны — нужна стратегия конфликтов.


Какую нагрузку ожидаем?

Здесь тоже важно избегать фантазий.

Очень часто в начале проекта звучит:

А если к нам одновременно придёт миллион пользователей?

При этом первая версия продукта будет обслуживать сто человек в месяц.

Нельзя игнорировать будущий рост.

Но проектировать инфраструктуру сразу под гипотетический миллион пользователей тоже дорого.

Правильнее понимать:

нагрузку сейчас;
реалистичный рост;
место потенциального bottleneck;
путь масштабирования.

Не существует «самого лучшего стека»

Иногда заказчик спрашивает:

Какой стек самый современный?

У этого вопроса нет полезного ответа.

Можно построить прекрасный продукт на технологиях, которым много лет.

И можно создать практически неподдерживаемую систему на самых новых framework.

Технология должна оцениваться не только по скорости выполнения кода.

Есть гораздо больше параметров:

КритерийЧто важно
ПроизводительностьХватает ли её реальной нагрузке
НадёжностьНасколько предсказуемо технология работает
ЭкосистемаЕсть ли библиотеки для нужных задач
ПоддержкаНасколько активно развивается платформа
КомандаКто будет поддерживать проект
НаймМожно ли найти специалистов
SecurityКак устроены обновления и зависимости
DeployНасколько сложно выпускать версии
ObservabilityНасколько легко диагностировать проблемы
СтоимостьСколько стоит разработка и инфраструктура
ИзменяемостьНасколько дорого развивать продукт

В итоге «чуть более быстрый» язык может оказаться худшим бизнес-выбором, если под него трудно найти разработчиков и каждая новая функция стоит в два раза дороже.


Frontend: сначала определяем тип интерфейса

Сегодня frontend часто автоматически ассоциируется с SPA.

Это не всегда оправдано.

Сначала полезно понять, что именно мы создаём.


Вариант 1. Контентный сайт

Например:

главная;
услуги;
кейсы;
экспертные статьи;
контакты;
формы.

Здесь особенно важны:

быстрый первый HTML;
SEO;
доступность;
простота;
минимальный JavaScript.

Строить всю систему как тяжёлое client-side SPA может быть излишним.

Подходы с SSR, SSG или обычным server-rendered HTML зачастую практичнее.


Вариант 2. Личный кабинет или CRM

Здесь интерфейс гораздо динамичнее.

Например:

таблицы;
фильтры;
модальные окна;
формы;
графики;
уведомления;
drag-and-drop;
real-time.

React, Vue, Svelte или другой компонентный framework уже может существенно упростить разработку.

Но даже здесь нужно спросить:

Нужен ли framework именно продукту или он нужен потому, что разработчик использует его везде?

React или Vue?

Универсального победителя нет.

Для большинства бизнес-продуктов обе технологии способны решить задачу.

Разница обычно проявляется в:

опыте команды;
экосистеме;
существующей кодовой базе;
требованиях к SSR;
будущем найме;
стиле архитектуры.

Именно поэтому профессиональный выбор редко звучит как:

React объективно лучше Vue.

Правильнее:

Для этого проекта React лучше соответствует опыту команды и выбранной архитектуре.

Это принципиально другая формулировка.


А иногда framework вообще не нужен

В одном из наших проектов frontend построен на нативных ES Modules без тяжёлого UI-framework.

Такой вариант даёт:

небольшой runtime;
контроль над загрузкой;
минимум внешних зависимостей;
прямое использование платформы браузера.

Но он имеет и цену:

часть инфраструктуры приходится писать самостоятельно;
сложнее масштабировать большую frontend-команду;
меньше готовых соглашений;
больше ответственности за архитектуру.

То есть это не «лучше React».

Это другой компромисс.


SSR нужен не каждому приложению

Server-side rendering полезен, когда важны:

индексация;
первоначальная загрузка;
social previews;
публичные страницы.

Но административная панель, открывающаяся после авторизации, может прекрасно жить как client-side приложение.

Иногда разумная архитектура сочетает оба подхода:

Публичная часть
→ SSR / SSG

Личный кабинет
→ client application

Нет необходимости заставлять весь продукт жить в одной модели рендеринга.


Backend: язык выбирается после понимания домена

Backend отвечает не просто за API.

В серьёзной системе здесь живут:

авторизация;
permissions;
бизнес-правила;
транзакции;
интеграции;
очереди;
files;
billing;
notifications;
audit.

Поэтому язык и framework нужно выбирать с учётом сложности именно этого слоя.


Node.js и TypeScript

Для многих B2B-продуктов это очень практичный вариант.

Преимущества:

один язык frontend/backend;
большая экосистема;
быстрая разработка;
хорошая работа с I/O;
удобные API и real-time сценарии.

Особенно хорошо подходит:

SaaS;
личным кабинетам;
CRM;
REST API;
WebSocket;
интеграционным сервисам.

Но Node.js не означает:

Никогда не будет проблем с производительностью.

CPU-тяжёлые операции лучше выносить в отдельные worker или специализированные сервисы.

Например:

видеообработка;
тяжёлое изображение;
ML inference;
массовые вычисления.

Python

Отлично подходит продуктам, где значительную роль играют:

AI;
ML;
Data Science;
аналитика;
automation;
scientific stack.

Django и FastAPI позволяют строить вполне серьёзные backend-системы.

Но выбирать Python только потому, что:

На нём быстро писать,

недостаточно.

Нужно оценивать тип нагрузки, concurrency, operational model и опыт команды.


Go

Go особенно интересен там, где важны:

простые независимые сервисы;
высокая concurrency;
сетевая работа;
низкое потребление ресурсов;
простая доставка бинарника.

Он может быть отличным решением для:

gateway;
integration service;
worker;
high-load API.

Но писать обычную небольшую административную систему на Go только ради слова «производительность» не всегда экономически оправдано.


Java и экосистема JVM

Для больших корпоративных систем Java остаётся очень сильным выбором.

Особенно когда:

большая команда;
долгий жизненный цикл;
сложный домен;
много интеграций;
строгие процессы;
enterprise-инфраструктура.

Цена — более тяжёлый старт проекта и обычно больший объём инфраструктуры.

Но в системе, которая должна развиваться десять лет большой командой, эта цена может быть вполне оправданной.


Производительность языка редко является первой проблемой

Для типичного B2B-сервиса запрос выглядит примерно так:

HTTP
 ↓
проверка авторизации
 ↓
SQL
 ↓
бизнес-логика
 ↓
JSON

Очень часто bottleneck находится не в скорости выполнения JavaScript или Python.

Он находится здесь:

неправильный SQL;
отсутствующий индекс;
N+1 queries;
огромный payload;
медленное внешнее API;
неправильный cache;
последовательные сетевые операции.

Поэтому смена Node.js на Go не исправит запрос, который делает 200 обращений к базе вместо двух.


Для большинства бизнес-продуктов PostgreSQL — очень сильная отправная точка

При проектировании нового сервиса иногда появляется желание сразу использовать несколько баз:

MongoDB;
PostgreSQL;
Redis;
Elasticsearch;
ClickHouse.

Каждая технология выглядит убедительно.

Но каждая новая система хранения означает ещё и:

backup;
monitoring;
security;
migration;
deployment;
обновления;
restore;
новую точку отказа.

Поэтому мы предпочитаем начинать с минимального набора.


Почему relational database обычно полезна

Бизнес-продукты часто содержат связанные сущности:

User
 ↓
Organization
 ↓
Project
 ↓
Stage
 ↓
Task

или:

Customer
 ↓
Order
 ↓
OrderItem
 ↓
Product

Здесь PostgreSQL даёт:

transactions;
foreign keys;
indexes;
constraints;
JSONB;
full-text capabilities;
mature tooling.

Для огромного количества веб-продуктов этого достаточно очень надолго.


NoSQL должен решать конкретную проблему

MongoDB или другой document store может быть отличным выбором.

Но причина должна быть более содержательной, чем:

У нас данные JSON, поэтому возьмём MongoDB.

PostgreSQL тоже умеет хранить JSON.

NoSQL становится действительно интересным, когда свойства данных и access patterns дают ему преимущество.

Например:

специфическая модель документов;
огромная горизонтальная запись;
определённые distributed workloads.

Архитектура должна следовать данным.

Не моде.


Redis — не обязательный компонент любого backend

Redis очень полезен.

Например, для:

cache;
sessions;
rate limiting;
distributed locks;
pub/sub;
временных данных.

Но добавлять Redis только потому, что:

Серьёзные проекты используют Redis,

не нужно.

Если у приложения небольшая нагрузка, PostgreSQL легко справляется с данными, а cache ничего не ускоряет, Redis становится ещё одним сервисом, который нужно:

запускать;
обновлять;
мониторить;
защищать.

Инфраструктура должна появляться тогда, когда решает измеримую проблему.


Очередь фоновых задач тоже бывает разной

Допустим, нужно:

отправить email;
обработать изображение;
выполнить webhook;
создать отчёт;
запустить AI-задачу.

Не обязательно сразу добавлять Kafka.

Для умеренной нагрузки queue может жить даже в PostgreSQL.

Например:

jobs
↓
SELECT ...
FOR UPDATE SKIP LOCKED
↓
worker

Можно получить:

retry;
heartbeat;
failed state;
concurrent workers.

Для многих продуктов этого достаточно.


Когда нужна отдельная очередь

RabbitMQ, Kafka или специализированная cloud queue становятся логичнее, когда появляются:

большой поток событий;
много независимых consumers;
высокая throughput;
event-driven architecture;
необходимость длительного хранения event stream.

Главное — не выбирать инструмент будущей проблемы раньше появления самой проблемы.


Object Storage нужно отделять от базы

Большие файлы плохо подходят для хранения непосредственно внутри основной transactional DB.

Чаще практичнее:

PostgreSQL
→ metadata

S3-compatible storage
→ bytes

Например:

attachments
images
audio
archives
documents

В базе хранится:

id;
owner;
filename;
size;
contentType;
objectKey;
createdAt.

Сам файл находится в object storage.

Так проще:

масштабировать;
делать CDN;
управлять lifecycle;
разделять backup;
выдавать signed URLs.

Но object storage создаёт новую проблему — распределённую консистентность

Допустим:

файл записали в S3
↓
SQL INSERT упал

Теперь появился orphan object.

Поэтому архитектура должна сразу отвечать:

Как он будет удалён?

Например:

try immediate cleanup
↓
если не получилось
↓
durable delete job

Каждый новый инфраструктурный компонент создаёт новые edge cases.

Это одна из причин не добавлять технологии «на всякий случай».


Поиск — отдельная система только тогда, когда обычного SQL действительно мало

Для большинства административных систем:

WHERE name ILIKE ...

или PostgreSQL Full Text Search могут быть вполне достаточны.

Elasticsearch/OpenSearch нужны, когда действительно важны:

сложная релевантность;
морфология;
фасеты;
большие индексы;
специализированный full-text.

Но вместе с ними появляется ещё один distributed index, который необходимо:

синхронизировать;
backup;
rebuild;
monitor.

Поэтому Elasticsearch не должен быть первым ответом на кнопку «Поиск».


Real-time: WebSocket нужен не потому, что он современный

Есть приложение, где уведомление обновляется раз в несколько минут.

Для него polling:

GET /notifications

может быть совершенно нормальным.

Есть чат.

Там:

WebSocket

гораздо естественнее.

Есть dashboard с десятками тысяч live-events.

Там архитектура становится ещё сложнее.

Не следует превращать обычную административную панель в distributed real-time platform без необходимости.


Docker: очень полезный инструмент, но не архитектура продукта

Docker позволяет стандартизировать:

runtime;
dependencies;
environment;
deployment.

Это особенно удобно, когда продукт состоит из:

backend;
worker;
PostgreSQL;
Redis;
reverse proxy.

Но присутствие Docker ещё не означает, что система хорошо спроектирована.

Можно прекрасно контейнеризировать плохую архитектуру.

Docker отвечает:

Как запустить компонент?

Он не отвечает:

Нужно ли вообще существование этого компонента?

Kubernetes нужен значительно реже, чем кажется

Это один из самых типичных примеров преждевременной сложности.

Kubernetes великолепно решает задачи:

оркестрации;
service discovery;
rolling deployment;
autoscaling;
self-healing;
управления большим числом контейнеров.

Но за это нужно платить:

инфраструктурной сложностью;
observability;
DevOps-компетенциями;
стоимостью поддержки.

Если продукт прекрасно помещается на:

1 VPS
+
Docker Compose
+
Nginx

перенос в Kubernetes не делает его автоматически более профессиональным.

Он просто делает инфраструктуру сложнее.


Один VPS может быть совершенно нормальным production

Иногда встречается противоположная ошибка:

Если всё находится на одном VPS, значит это несерьёзно.

Это тоже неверно.

Представим небольшой B2B SaaS:

2–4 CPU
4–8 GB RAM
NVMe

На нём:

Nginx;
2 API process;
worker;
PostgreSQL;
Redis.

При умеренной нагрузке такой сервер может прекрасно обслуживать продукт.

При этом deployment, backup и monitoring намного проще, чем у распределённого кластера.

Хорошая архитектура должна иметь путь масштабирования, а не обязательно начинаться с конечной инфраструктуры большого бизнеса.


Монолит — не плохое слово

Для большинства новых продуктов мы бы значительно охотнее начинали с модульного монолита, чем с микросервисов.

Например:

Application
│
├── Auth
├── Users
├── Projects
├── Billing
├── Files
├── Notifications
└── Integrations

У модулей:

явные границы;
свои services;
свои repositories;
свои domain rules.

Но deploy пока один.

Преимущества:

простые транзакции;
простая отладка;
один deployment;
меньше network failure;
проще локальная разработка.

Если один модуль действительно вырастет настолько, что его нужно отделить, сделать это можно позже.


Микросервисы должны появляться из давления системы

Хорошими причинами могут быть:

разные команды;
независимое масштабирование;
изоляция отказов;
разные deployment cycles;
специальные runtime requirements.

Плохая причина:

Netflix использует микросервисы.

Netflix решает задачи Netflix.

Новый B2B-сервис должен решать свои.


Один из главных критериев хорошего стека — стоимость изменения

Представим, сегодня клиент просит:

Добавить новый статус проекта.

В хорошо спроектированной системе изменение затрагивает:

enum/status model;
валидацию;
UI;
несколько тестов.

В плохо спроектированной:

три микросервиса;
две очереди;
четыре event schema;
несколько deployment pipelines.

Хотя продукт выполняет ту же бизнес-функцию.

Поэтому при выборе стека мы оцениваем не только:

Насколько быстро система отвечает сегодня?

Но и:

Насколько дорого в ней менять бизнес завтра?

Для коммерческого продукта второй вопрос иногда важнее первого.


TypeScript everywhere — полезно, но не является целью само по себе

Frontend и backend на одном языке имеют очевидные преимущества.

Можно разделять:

types;
validation schemas;
API contracts.

Команде проще переключаться между слоями.

Но это не означает, что все компоненты обязаны быть написаны на TypeScript.

Например:

основной backend → TypeScript
ML worker → Python
performance-critical service → Go

может быть значительно разумнее, чем искусственно заставлять всю систему жить в одном runtime.

Полиглотная архитектура оправдана, когда каждый язык решает реальную специальную задачу.


API-контракт важнее выбора HTTP-framework

Можно бесконечно спорить:

Express
Fastify
NestJS
FastAPI
Spring
Gin

Но для развития продукта намного важнее:

как версионируется API;
как возвращаются ошибки;
как устроена авторизация;
есть ли idempotency;
как работает pagination;
какие гарантии у mutations.

Плохо спроектированный API останется плохим независимо от framework.


Security должна влиять на стек с самого начала

Безопасность — не отдельный plugin после завершения разработки.

Например, при выборе архитектуры нужно заранее понимать:

где хранятся secrets;
как хешируются пароли;
как устроены sessions;
кто проверяет permissions;
как выдаются files;
как работает audit;
что находится во frontend.

Если система хранит конфиденциальные данные, могут понадобиться:

application-level encryption;
key rotation;
TOTP;
short-lived sessions;
object ownership;
signed URLs.

Эти требования влияют на архитектуру значительно сильнее, чем цвет UI-библиотеки.


Авторизация и permissions тоже нужно разделять

Очень частая ошибка:

Пользователь авторизован
→ значит объект можно вернуть.

Но в SaaS должно быть:

Кто пользователь?
        ↓
В какой организации он состоит?
        ↓
Какая у него роль?
        ↓
Принадлежит ли объект этой организации?
        ↓
Есть ли право на конкретное действие?

Ни React, ни PostgreSQL сами по себе этого не решат.

Это domain architecture.


Стек должен учитывать backup и restore

Иногда инфраструктуру выбирают по удобству разработки и совершенно забывают:

Как всё это восстанавливать?

Если в системе:

PostgreSQL;
Redis;
Elasticsearch;
Kafka;
MinIO;
MongoDB;

нужно понимать backup/restore каждого компонента и отношения между их состояниями.

Чем больше persistent systems, тем сложнее согласованное восстановление.

Поэтому минимизация количества источников истины — очень практичная стратегия.


Monitoring тоже является частью технологического стека

Production-система должна отвечать как минимум на вопросы:

API работает?
База доступна?
Очереди не застряли?
Сколько ошибок?
Сколько свободного диска?
Свежий ли backup?
Когда заканчивается TLS?

Для небольшого продукта необязательно сразу разворачивать огромный observability cluster.

Можно начать с:

структурированных логов;
health/readiness;
метрик;
простого production monitor;
внешнего alerting.

А затем развивать систему по мере реальной необходимости.


Хороший стек должен иметь понятный путь роста

Представим первую версию:

Nginx
  ↓
Node.js
  ↓
PostgreSQL

Продукт вырос.

Первый bottleneck — тяжёлые задачи.

Добавляем:

Worker

Затем появляются hot reads:

Redis

Файлов становится много:

S3

Нужен full-text:

Search service

Это естественный рост.

Гораздо здоровее, чем начинать с:

Kubernetes
Kafka
Redis
Elasticsearch
5 microservices

и только после этого искать первых клиентов.


Пример практичного стека для обычного B2B-веб-сервиса

Представим продукт:

регистрация;
личный кабинет;
проекты;
файлы;
роли;
уведомления;
админка;
интеграции.

Один из вполне разумных стартовых вариантов:

Frontend
TypeScript
React / Vue

Backend
Node.js
TypeScript

Database
PostgreSQL

Cache / runtime
Redis — только если нужен

Files
S3-compatible Object Storage

Reverse proxy
Nginx

Deployment
Docker / Docker Compose

OS
Linux

Background
Worker + PostgreSQL queue
или специализированная queue при необходимости

Monitoring
health/readiness + logs + metrics + alerts

Это не «лучший стек на свете».

Но у него хорошие свойства:

понятная разработка;
широкая экосистема;
доступные специалисты;
простая инфраструктура;
понятный путь масштабирования.

Стек для контентного экспертного сайта будет другим

Например:

SSR / SSG frontend;
минимальный JavaScript;
PostgreSQL или CMS;
object storage;
CDN;
Nginx;
сильный SEO layer.

Redis и WebSocket могут вообще не понадобиться.

И это не делает продукт менее технологичным.

Наоборот — хороший стек часто выглядит удивительно скучно.


Для high-load сервиса появляются другие требования

Если продукт действительно обрабатывает:

десятки тысяч requests/sec;
огромный event stream;
высокочастотные данные;
массовые параллельные соединения,

тогда выбор меняется.

Могут появиться:

Go / Java;
Kafka;
ClickHouse;
distributed cache;
sharding;
orchestration;
autoscaling.

Но это должно вытекать из измеримой нагрузки.

Не из желания сделать архитектурную диаграмму впечатляющей.


Как понять, что стек переусложнён

Есть несколько характерных признаков.

Первый:

Для запуска проекта на ноутбуке нужно поднять пятнадцать сервисов.

Второй:

Никто точно не знает, зачем используется часть инфраструктуры.

Третий:

Изменение одного поля требует обновления нескольких сервисов.

Четвёртый:

На поддержку инфраструктуры уходит больше времени, чем на продукт.

Пятый:

Команда объясняет архитектуру будущими миллионами пользователей, которых пока нет.

Шестой:

Большинство технологий добавлены «на будущее».

Это не обязательно означает, что архитектура неправильная.

Но каждый такой пункт — повод задать дополнительные вопросы.


Как понять, что стек слишком простой

Есть и противоположная проблема.

Например:

всё хранится в JSON-файле;
backup отсутствует;
один Node process;
uploads лежат рядом с кодом;
нет migrations;
нет мониторинга;
secrets записаны в repository.

Простота хороша только пока она не уничтожает надёжность.

Минимальный стек — это не стек без инфраструктуры.

Это стек без ненужной инфраструктуры.


Технологическая зрелость не измеряется количеством компонентов

Иногда архитектурная схема выглядит так:

Client
 ↓
API Gateway
 ↓
Kafka
 ↓
Service A
 ↓
Service B
 ↓
Redis
 ↓
Database

И создаёт ощущение серьёзной системы.

Но если бизнес-операция могла выглядеть:

Client
 ↓
Backend
 ↓
PostgreSQL

мы получили не зрелость.

Мы получили больше:

network hops;
failure modes;
logs;
deployments;
monitoring;
стоимости.

Хороший архитектор умеет добавлять сложность.

Но ещё важнее — уметь не добавлять её без необходимости.


Как мы оцениваем стек с точки зрения заказчика

Заказчику не обязательно понимать внутреннее устройство V8 или event loop.

Но есть вопросы, которые полезно задать подрядчику.

Почему выбрана именно эта технология?

Хороший ответ:

У нас сложный динамический кабинет, много client-side interaction и команда хорошо работает с React.

Плохой:

Сейчас все делают на React.

Что произойдёт при росте нагрузки?

Хороший ответ описывает bottleneck и путь масштабирования.

Не обязательно уже иметь кластер.

Но должен существовать план.


Где будет храниться база?

Нужно понимать:

кому принадлежит;
как делается backup;
как делается restore.

Как обновляется приложение?

Должно быть понятнее, чем:

Разработчик подключается по SSH и что-то меняет.

Можно ли заменить разработчика?

Очень важный вопрос.

Если продукт может поддерживать только один человек, который знает собственный экзотический framework, это бизнес-риск.


Как передаётся проект?

Клиенту должны быть понятны:

исходники;
инфраструктура;
доступы;
домен;
database;
documentation;
secrets.

Стоимость стека — это не только стоимость VPS

Допустим, есть два варианта.

Вариант A

VPS:

3 000 ₽ / месяц

но система понятная и требует мало DevOps.

Вариант B

Infrastructure:

2 000 ₽ / месяц

но каждое обновление занимает на два часа больше.

Экономия тысячи рублей на инфраструктуре может совершенно потеряться на стоимости разработки.

И наоборот: иногда небольшой технический redesign способен сократить инфраструктурные расходы в разы.

Поэтому нужно считать TCO — total cost of ownership:

разработка;
серверы;
support;
DevOps;
backup;
monitoring;
обновления;
миграции;
найм.

Дешевле разработать — не всегда дешевле владеть

Можно быстро собрать MVP из множества managed-сервисов.

На старте это удобно.

Но после роста:

database;
auth;
storage;
functions;
analytics;
email;
queue

могут оказаться у разных поставщиков с отдельными тарифами.

И наоборот, всё размещать самостоятельно только ради экономии тоже может быть ошибкой: обслуживание инфраструктуры имеет собственную стоимость.

Выбор между managed и self-hosted тоже является частью технологического стека.


Нужно учитывать стоимость выхода из технологии

Есть вопрос, который редко задают:

Насколько трудно будет заменить этот компонент?

Например:

PostgreSQL

имеет огромную экосистему и достаточно стандартную модель.

А proprietary service с уникальным API может создать сильный vendor lock-in.

Lock-in не всегда плох.

Managed-продукт может дать огромную экономию времени.

Но зависимость должна быть осознанной.


Не нужно выбирать стек на десять лет вперёд

Это ещё одна крайность.

Невозможно точно знать архитектуру продукта через десять лет.

Можно выбрать:

хорошую базу;
чёткие границы;
миграции;
API contracts;
модульную структуру;
нормальную observability.

А затем адаптировать систему.

Архитектура — не предсказание будущего.

Это способность переживать изменения будущего с приемлемой стоимостью.


Как выглядит наш порядок выбора

Если сильно упростить, мы идём примерно так.

Шаг 1. Определяем бизнес-процесс

Что продукт делает?


Шаг 2. Выделяем критичные данные

Что нельзя потерять?


Шаг 3. Определяем тип нагрузки

read-heavy;
write-heavy;
real-time;
batch;
CPU-heavy;
I/O-heavy.

Шаг 4. Определяем публичную часть

Нужны ли:

SEO;
SSR;
SSG;
PWA;
offline.

Шаг 5. Выбираем основной storage

Обычно начинаем с минимального количества источников истины.


Шаг 6. Проектируем backend

Сначала domain boundaries.

Потом framework.


Шаг 7. Добавляем инфраструктуру только под конкретные задачи

Redis
Queue
S3
Search
WebSocket

не появляются автоматически.


Шаг 8. Проектируем production

Ещё до конца разработки нужно понимать:

deploy;
backup;
rollback;
monitoring;
security;
secrets.

Шаг 9. Проверяем стоимость поддержки

Кто будет сопровождать систему через два года?


Шаг 10. Проверяем путь роста

Что делать, если пользователей станет:

в 10 раз больше;
в 100 раз больше?

Не обязательно реализовывать это сейчас.

Но архитектура не должна создавать тупик.


Хороший технологический стек часто оказывается довольно простым

После всех архитектурных обсуждений можно прийти к чему-то вроде:

TypeScript
React
Node.js
PostgreSQL
S3
Nginx
Docker

И это нормально.

Технологическая экспертиза проявляется не в количестве инструментов.

Она проявляется в понимании:

Почему здесь нет Redis?
Почему здесь не нужны микросервисы?
Почему файлы вынесены из PostgreSQL?
Почему публичная часть рендерится на сервере?
Почему worker отделён от API?
Почему queue пока находится в PostgreSQL?

Если на каждый вопрос есть техническое объяснение, стек выбран осознанно.


Когда стек пора менять

Технологии не нужно менять только потому, что вышел новый framework.

Хорошими причинами для изменения являются:

измеримый bottleneck;
проблема поддержки;
отсутствие security updates;
ограничение масштабирования;
слишком высокая стоимость эксплуатации;
невозможность реализовать ключевое требование.

Плохая причина:

В Twitter сейчас много говорят про другую технологию.

Переписывание работающей системы — один из самых дорогих способов следовать моде.


И когда переписывание всё же оправдано

Иногда legacy действительно становится настолько дорогим, что дальнейшая поддержка хуже миграции.

Но тогда решение должно опираться на конкретные показатели.

Например:

релизы занимают недели;
критичные библиотеки больше не поддерживаются;
новые разработчики не могут работать с системой;
архитектура не выдерживает необходимую нагрузку;
security невозможно обеспечить приемлемо.

Тогда можно проектировать постепенный переход.

Не обязательно выбрасывать всё приложение одновременно.


Технологический стек должен соответствовать стадии продукта

Идея / прототип

Главное:

быстро проверить гипотезу.

MVP

Главное:

построить минимально надёжный полноценный процесс.

Product-market fit

Главное:

ускорять развитие продукта.

Рост

Появляются:

масштабирование;
observability;
performance;
автоматизация.

Зрелый продукт

Особенно важны:

надёжность;
security;
стоимость владения;
совместимость;
migration strategy.

Один и тот же продукт совершенно нормально может менять часть технологий по мере перехода между этими стадиями.


Что особенно важно потенциальному заказчику

Для заказчика вопрос:

На чём будет написан сайт?

часто менее важен, чем кажется.

Гораздо полезнее спросить:

Почему этот стек выбран для моего продукта?
Как он будет масштабироваться?
Насколько легко найти другого разработчика?
Где будут находиться мои данные?
Как делается backup?
Что произойдёт при падении сервера?
Как обновляется приложение?
Кому принадлежит инфраструктура?
Какие компоненты действительно необходимы?

Именно ответы на эти вопросы показывают зрелость технического решения.


Вместо вывода

Выбор технологического стека — это не конкурс framework.

React не делает продукт автоматически хорошим.

Go не делает его автоматически быстрым.

Kubernetes не делает его автоматически масштабируемым.

Микросервисы не делают его автоматически современным.

И PostgreSQL не подходит абсолютно для всего.

Каждая технология имеет смысл только в контексте задачи.

Поэтому мы считаем правильной последовательность:

Бизнес-задача
      ↓
Данные
      ↓
Нагрузка
      ↓
Риски
      ↓
Требования к развитию
      ↓
Архитектура
      ↓
И только потом
технологии

Хороший стек должен быть достаточно мощным, чтобы решать текущую задачу.

Достаточно простым, чтобы команда могла его понимать.

Достаточно распространённым, чтобы продукт не зависел от одного человека.

Достаточно гибким, чтобы пережить несколько лет развития.

И достаточно экономичным, чтобы технологическая инфраструктура не стала дороже самой бизнес-ценности продукта.

Иногда результатом такого анализа действительно становится большой распределённый стек.

Но гораздо чаще хорошая архитектура начинается с нескольких проверенных компонентов и ясного ответа на вопрос:

какую конкретную проблему решает каждый из них?

Если такого ответа нет, возможно, этот компонент продукту пока вообще не нужен.

Есть похожая задача?

Опишите продукт, интеграции и ограничения. До разработки зафиксируем объём, риски и критерии приёмки.

Обсудить проект →