Architecture First: как в одиночку с ИИ сделать альтернативу Jira — Часть 4.
Предыдущая статья определила первый пользовательский путь (user path) приложения. Настало время выбрать для него стек.
Решения по стеку принимаются после прототипирования интерфейса приложения и определения возможных действий пользователя. Выбирать базу данных или фреймворк до того, как будут понятны типичные сценарии использования — путь к потенциальным проблемам. К этому моменту мы имели HTML-прототипы, сгенерированные AI, поэтому могли делать осознанный выбор.
Если кратко: это .NET 10 и C# на бэкенде, PostgreSQL 18 для хранения данных, Nuxt 4 и Vue 3 с TypeScript на фронтенде. Стек выбирался по правилу — предпочитать стабильные технологии и держать зависимости на минимуме. В этой статье расскажем, как это правило применяется к каждой выбранной технологии.
Бэкенд: .NET 10 и C#
.NET — наш основной профессиональный стек, и мы используем его для бэкенда по умолчанию. Исключения появляются, когда на вопрос "С какими проблемами мы столкнемся, если будем использовать .NET в этом проекте" появляются весомые аргументы.
Из этого правила бывают исключения — иногда мы хотим близко познакомиться с технологией, и лучший способ это сделать — разработать реальный проект. Но в таком случае важно понимать, что путь может оказаться долгим: обучение и разработка идут одновременно, код приходится переписывать по мере погружения в технологию.
Цель этого проекта — выпустить продукт корпоративного уровня небольшой командой, сделать это быстро и недорого, и задокументировать весь пройденный путь в виде серии статей. Поэтому мы старались выбирать максимально знакомые технологии.
База данных: PostgreSQL 18
PostgreSQL выбран по той же причине, что и .NET. Мы знаем, что её производительности хватит для всего, с чем столкнётся проект. Экосистема расширений позволяет эффективно решать почти любые задачи — полнотекстовый поиск, партиционирование, временные ряды.
Это доверие возникло не на пустом месте. Многие из нас работали в бигтехе, и переход с SQL Server на PostgreSQL в высоконагруженной системе был типичным кейсом. Бывали нюансы, но в целом система работала стабильно. Postgres бесплатна — а это важно, чтобы получить окупаемый проект. Домен таск-трекера хорошо ложится на логику реляционных БД, поэтому экзотику вроде MongoDB или Neo4j мы даже не рассматривали. Про нестандартные базы у нас даже есть небольшая история.
Ошибка: выбор MongoDB и переезд на PostgreSQL
В 2020 году база MongoDB была на пике хайпа; ее, казалось, начали использовать все вокруг. Появилось желание использовать MongoDB для следующего проекта: не потому что проекту это было нужно, скорее, хотелось быть в тренде. Следующим проектом был веб-краулер — SaaS, помогающий парсить сайты через визуальный интерфейс. Мы убеждали себя, что документное хранилище как раз идеально подойдет, чтобы хранить JSON'ы с собранными данными, а бессхемная модель позволит сэкономить время на миграциях.
На самом же деле проекту нужна была обычная реляционная база: почти все таблицы сервиса (пользователи, проекты, истории краулинга) имели отношения, которые было неудобно запрашивать через MongoDB. А те JSON'ы с результатами краулинга нужны были лишь для возвращения пользователю по запросу; их вообще можно было хранить в файловом хранилище.
По мере роста проекта запросы, которые в реляционной базе были бы тривиальными, становились все сложнее. На написание оптимального запроса вполне мог уйти рабочий день. В конце концов на это надоело тратить время, мы потратили неделю или две и переписали всё на PostgreSQL.
На будущее мы определились: по умолчанию используем — реляционную базу, если имеем нестандартный случай — начинаем исследование, что подойдет лучше всего под этот случай.
Высокоуровневое проектирование базы перед ее выбором
В начале проекта мы думаем о данных высокоуровнево: какие таблицы могут в теории появиться у домена и как они будут связаны. Необходимо представить случаи, с которыми мы, скорее всего, столкнёмся, и подумать, как мы их будем обрабатывать в случае выбора конкретной базы.
Для Boards трудными представлялись следующие ситуации.
Полнотекстовый поиск. Поиск по issues — стандартная функциональность таск-трекера. Встроенного FTS в PostgreSQL должно хватить на долгое время, в будущем связанную с поиском функциональность можно будет вынести в специализированную базу.
Кастомные атрибуты. Пользователи должны иметь возможность создать свои атрибуты для issues (например, тип задачи, выполнить до и т.п.). План — реализовать их как таблицу на каждый тип атрибута, а не как JSON-блоб. Определения атрибутов живут в одной таблице — имя, цвет, тип, — с деталями конкретного типа в таблицах вроде text_attributes или date_attributes. Значения атрибутов будут находиться в типизированных таблицах вроде: text_attribute_values со значениями типа varchar, date_attribute_values со значением типа datetime, и так далее. Такая форма хранения должна корректно работать с индексами и поддерживать пользовательскую валидацию для атрибутов — например, текстовый атрибут не более 10 символов, числовой с диапазоном 24–42.
Права доступа. Наши сотрудники имели опыт реализации управления правами пользователей в реляционной базе, поэтому не было причин ждать, что Boards окажется исключением.
Все кейсы казались реализуемыми на реляционной БД. Забегая немного вперед — получившиеся модели можно посмотреть в папке DataAccess/Models бэкенд-репозитория.
Фронтенд: Nuxt 4 и Vue 3
Так получилось, что среди нас нет профессиональных фронтендеров. Когда-то давно мы попробовали использовать Vue в своих проектах (тогда это была еще версия 2) — и он нам понравился. С тех пор просто используем его всегда — проблем никогда не замечали. Главное не забыть добавить в проект TypeScript — иначе разработка клиентской части превращается в ходьбу по минному полю — не знаешь, где тебя будет поджидать очередная ошибка.
Выбор Nuxt 4 обусловлен богатой экосистемой фреймворка и его стабильностью. У нас был недавний опыт: этот блог построен на Nuxt 4, и было заметно, что фреймворк очень функциональный — все, что только может понадобиться уже реализовано в ядре или в виде плагина.
В первых итерациях приложение будет запускаться как SPA, но в дальнейшем могут пригодиться следующие особенности фреймворка.
- i18n. Приложение будет сделано с поддержкой русского и английского языков. Русскоязычная аудитория — одна из крупнейших в Telegram, и это наш родной язык; английский — язык по умолчанию для всех остальных.
- SSR. Возможно, в будущем мы можем дать пользователям делать доски публичными: например, чтобы делиться прогрессом. Доски содержат тексты — заголовки issues, описания. Такой контент может индексироваться поисковиками. Nuxt позволит включить SSR парой строчек.
- SEO. Та же логика, что и для публичных досок: при необходимости правильное SEO настраивается из коробки очень быстро.
В следующих итерациях мы точно планируем перейти с SPA на MPA. Таск-трекером удобнее пользоваться, когда любой ссылкой можно поделиться — доской, конкретным issue, отфильтрованным view. Это предполагает отдельные URL для страниц, и Nuxt такое поддерживает. Пока же видение приложения не окончательно, начинать разработку будем с SPA.
Итоги
Стек оказался всем известен и даже скучен. На бэкенде — C# и .NET, с которыми мы знакомы почти 10 лет. База — PostgreSQL, она проверена временем и нагрузками. Фронтенд — Vue + TypeScript + Nuxt — связка, уже использовавшаяся в блоге. Новизна должна быть в продукте, а не в инструментах.
Что дальше
Со стеком определились — пора написать немного кода. Следующая статья о первой итерации бэкенда: схема базы данных, Telegram-хост, который сохраняет сообщения пользователя, CI-пайплайн для билда бэкенда и отправки артефактов на сервер.