Это третья статья в цикле "Выбор стека технологий".
1. Выбор стека технологий (введение и кейсы) 1. Выбор стека технологий (доступность и поддерживаемость)
В первой статье мы определили содержание стека технологий. Это:
- хранилище данных
- бэкенд-фреймворк
- фронтенд-фреймворк
- системы виртуализации/контейнеризации
- API
- DevOps (IaC, мониторинг, логгирование), например, ELK (Elasticsearch, Logstash, Kibana)
Во второй статье мы рассмотрели вопрос выбора стека с точки зрения атрибутов качества - доступность и поддерживаемость
В этой статье мы посмотрим на вопрос выбора стека ещё шире, глубже и конкретнее.
Critical thinking is the analysis of available facts, evidence, observations, and arguments to form a judgment.
Edward M. Glaser. "Defining Critical Thinking (opens new window)"
Критическое мышление — способность человека ставить под сомнение поступающую информацию, включая собственные убеждения.
Википедия
На что не стоит опираться при выборе стека:
- Личные предпочтения
- Хайп
- Советы коллег из других компаний
- Звездочки на Github-е
- Опросы на Stackoverflow
На что стоит делать упор:
- Личный опыт успешных проектов
- Доказанный опыт успешных проектов других компаний
- Простоту и удобство использования
- Наличие прототипов, реализующих задуманное на конкретной технологии
- Отношение потенциальной пользы от внедрения к трудопотерям на внедрение
Список вопросов, на которые стоит ответить при выборе любого решения, не только стека:
- Как долго эта технология будет жива и пользоваться спросом? Flash, Pascal, VHS - популярные в прошлом, но забытые сейчас. Нужно брать технологии, у которых впереди в запасе как минимум 10 лет развития.
- Легко ли обслуживать эту технологию? "Используя популярный тулсет вы с легкостью найдете специалиста" именно этим руководствовались миллионы проектов которые взяли React в качестве фронтенд-фреймворка. В итоге множество проектов завязли в некачественном коде из-за отсутствия архитектуры. Если вам нужно быстро выйти на рынок, проще всего взять PHP и Vue. Но на долгой дистанции с ними будет много мучений.
- Какие обременения и накладные расходы у этой технологии? О выгодах мы задумываемся автоматически. Они ослепляют. Каждая новая технология это +1 человек к команде. Typescript - кто-то должен писать типы, UI Material - кто-то должен читать документацию, Docker - отдельный человек для написания helm-charts и тд.
- Если это миграция - возможен ли откат, какова цена восстановления в случае неудачи?
- Если это внедрение нового - какова стоимость внедрения и сроки окупаемости?
Помимо всего прочего, важна экспертная оценка реализации проекта. Желательно иметь подробный ресурсный план для каждой технологии, написанный признанным экспертом.
Может оказаться, что реализация на Java и React займет год и 100 миллионов, но на Go и Angular - полгода и 20 миллионов. Выбор будет очевиден.
# Пример переусложнения
В этой выдуманной истории огромное количество скрытой иронии, прошу прощения.
Итак, представьте, у генерального директора Андрея появилась идея на миллиард, и даже предварительные договоренности о продажах. Он подходит к своему зятю-программисту Алексею и говорит - нужно сделать проект, поможешь?
Андрей назначает Алексея CTO (станция техобслуживания) и ждет первых результатов.
Алексей, не будучи дураком, начинает мыслить глобально, как настоящий профессионал.
Он начинает с Frontend-а. Язык - без вариантов - JavaScript. Точнее, его типизированная ипостась - Typescript. Из статей в интернете он узнает что у Angular очень высокий порог вхождения, Vue имеет слабую поддержку одним человеком, а jQuery уже устарел и выбирает React. Ведь на рынке огромная толпа разработчиков на React и астрономическое количество готовых компонентов для него. К тому же React не ограничивает архитектуру, гибкость инструмента способствует быстрой разработке.
Также он узнает что Bootstrap уже не моден и в качестве компонентной библиотеки выбирает Material-UI. А в качестве CSS-фреймворка Tailwind.
Также, для фронтенда ему пригодятся Webpack, icomoon и сотня-другая компонентов для реализации задуманного, включая websocket и canvas.
Ах да, он чуть не забыл про State-manager, коих уже десятки, он выбирает самый популярный Redux, ведь, чем больше boilerplate-кода, тем стабильнее и надежнее приложение.
Итак, на фронтенде - React, Redux, Material-UI, Tailwind, Webpack и много всего остального.
Бэкенд - тут Алексей решил не переусложнять, а оставить один язык для всего приложения и на сервере будет запущено приложение на Node.js. Повыбирав из фреймворков (Next.js, Nuxt.js, etc.js...), он решил взять то, что не имеет строгой привязки к фреймворку - Nest.js.
База данных - конечно MongoDB, ведь в интернете написано что она очень быстрая и гибкая. Но, на всякий случай, будем использовать ее как реляционную, все сущности будут связаны и лежать в отдельных коллекциях. Рядом будет in-memory cache Redis на случай большой нагрузки.
В качестве API Алексей решил сделать прямое соединение через Websocket. Это быстро, модно и просто.
Подумав ещё, Алексей решил сразу создать пайплайны CI/CD, и сразу запускать Docker-контейнеры в облако с помощью helm-чартов - в Kubernetes кластер, чтобы иметь возможность горизонтально масштабировать приложение автоматически.
А чтобы контейнеров было побольше - для пущей модульности - сразу решил разделить приложение на микросервисы и положить в разные репозитории, общение организовать через Kafka, а базу данных - реплицировать на три ноды, так точно будет работать быстрее.
Прошло несколько лет, продукт наконец вышел, рос медленно, но верно и продажи поднялись - продукт получил популярность, даже с такими высокими ценами. Алексей решает набрать команду и нанимает 7 фулл-стэк разработчиков. К этому моменту и в последствии, tailwindcss и material-ui использованы в 10% кода, React-компоненты встречаются и в виде классов, и в функциональном стиле со множеством перегруженных кастомных хуков.
А тем временем приложение начинает падать по три раза в день от наплыва пользователей - база данных съедает всю память на самой большой виртуальной машине в облаке потому что деплой в Kubernetes так и не смогли настроить ни подрядчики ни весь отдел разработки, хотя пытались по очереди все. Kafka тоже не поднялась.
Также возникают проблемы с уведомлениями - письма не доходят - падают в спам, заказчики не получают вовремя отчеты, а работа идет все медленнее и медленнее из-за количества багов ввиду излишней гибкости фронтенда.
Поскольку CTO не разбирался в серверах и у него никогда не было больших проектов, место на диске съели логи, продуктовый сервер был настроен абы как, а доступ к нему был у половины разработчиков в любое время без надзора. Разделения сред тоже не продумали, тестовый сервер оккупировал менеджмент для демонстраций заказчику, ломать его было нельзя, данные мало были похожи на реальные.
Наш CTO понимает что пора - пора осуществить задуманное - переехать с MongoDB на RDBMS, например, PostgreSQL, но количество написанных без ORM агрегаций, которыми так славится MongoDB, выглядело неподъемным. Redis не помогал, а больше мешал. Типы были перегруженными, а конвертация данных - повсеместной. В проекте насчитывались тысячи вызовов map и reduce.
Плюс, заказчики стали настаивать на том, чтобы приложение работало оффлайн (эту функцию откладывали до последнего, как слишком простую, были задачи поинтереснее, с canvas-ом). И тут выясняется, что нужно загрузить несколько гигабайт информации на устройство пользователя. А при каждом обновлении приложения эти данные стирать, потому что схема изменилась, то есть, писать миграции для State manager-а.
Генеральный директор, видя что приложение падает под напором, предлагает самым крупным заказчикам беспрецендентный шаг - ради их же безопасности, компания будет устанавливать приложение на сервера заказчика. Первая установка прошла успешно, вторая тоже, но автоматизировать процесс установки не получается - можно только вручную и особо обученным сотрудником - человеком, разбирающемся в приложении, интеграциях и unix-серверах. Такой стоит недешево.
В общем и целом, оверинжиниринг во всей красе - 12 окружений которые нужно обновлять только вручную и только по выходным, плюс множество технологий, которые сильно усложняют общую картину, хотя, казалось бы, призваны решать проблемы.
Цена упрощения такого проекта - примерно 10 миллионов рублей. Это выделенная команда для миграции из 6 человек на год работы.
Несмотря на то, что проект успел заработать в несколько раз больше - такой огромный технический долг его может потопить.
# Пример упрощения
В целом, стэк был выбран разумно, но к переусложнению привели другие факторы. Время разобраться.
Давайте сначала выпишем ошибки, которые были допущены выше:
- Использование MongoDB как реляционной базы данных привело к дорогим проблемам производительности
- Чрезмерная преждевременная оптимизация и решение вымышленных проблем масштабирования привело к потере времени на настройку
- Назначение CTO малокомпетентного и неопытного человека
Исходя из этого, фундамент проекта стоило заложить так:
- Использовать MongoDB в качестве хранилища, но как NoSQL - так будет проще работать с данными - агрегации все равно запускаются на JavaScript, также будет проще переходить на реляционную базу - запросы будут проще, да и на несколько баз разделить проще
- Ввести правило оценивать трудозатраты и потенциальную выгоду от реализации фич, таким образом ни функциональность, ни архитектура решения не будут страдать от жажды скорой наживы
- Начать с самого простого - с монолита, так как очень редко встречаются настолько огромные проекты чтобы вводить микросервисную архитектуру
- Поставки приложения - в виде образа виртуальной машины, который достаточно просто развернуть в среде заказчика, руками самого заказчика
- Описать архитектуру проекта в виде 4D и менять по мере необходимости
- На ранних стадиях ввести мониторинг систем, установить ELK стэк и настроить режим логгирования, бэкапа, управления инцидентами
- Следовать принципу "проще - лучше", причем, "так быстрее" - почти всегда плохо и приводит к сложностям