Когда я оцениваю проекты, команды или даже отдельных людей, я прикидываю количество управляющих воздействий — сколько раз мне нужно контактировать с командой, чтобы нужные вещи случались. Чем меньше — тем лучше.
Грубо говоря — плохая команда не едет вперёд без дейликов. Несамостоятельный сотрудник не выполняет задачи без десятка комментариев от меня. Плохо настроенный проект буксует без ежедневных встреч всех участников.
Это как с машинами — от Москвы до Воронежа можно доехать на мерседесе или на жигулях примерно за одно и то же время. Только на мерседесе будешь разглядывать дорогу и слушать музыку, а на жигулях придётся подруливать, следить за температурой двигателя и быть готовы починить машину, если что-то отвалится.
Правда, в отличие от машин, ответственные сотрудники хотят не на порядок больше денег, чем обычные.
Вебиум — онлайн-школа для подготовки к ЕГЭ. 30 тысяч школьников, тысяча наставников, 20 тысяч вопросов и 2 тысячи домашних заданий на сотни тем.
В 2021 году у Вебиума уже была рабочая система на Ruby, которую разрабатывали подрядчики-аутcорсеры. К нам они обратились с привычной проблемой — подрядчики медленно пилят фичи. Посмотрев код и оценив возможности команды, мы поняли, что рефакторить существующий код — долго и дорого, и решили перезапустить систему своими руками на привычном стеке — Django и Nuxt.js. Справились за год: с сентября 2022 все ученики покупают и проходят курсы в новой системе.
Перед нами стояли 3 задачи:
Сделать так, чтобы фичи, которые хочет бизнес, разрабатывались быстрее и более предсказуемо. Обострённый пример: «давайте добавим вот эту маленькую штуку; конечно, будет завтра (возвращаются через две недели), ой нет, это займёт полгода» (и это ещё хорошо, если вернутся с таким честным ответом).
Нанять технического директора и создать внутреннюю команду разработки, чтобы в будущем не зависеть от аутсорса.
Сделать всё это «на лету», без остановки образовательного процесса и потерь для бизнеса.
Дедлайн всего проекта — сентябрь 2022. Министерство образования не станет переносить ЕГЭ из-за того, что мы не успели доделать, например, отправку уведомлений в VK.
Чтобы не подписываться сразу на гигантский годовой проект, мы разбили работу на 2 больших этапа — запуск нового магазина и запуск новой платформы обучения (Learning Management System, LMS). Магазин – часть сайта, где школьники и родители покупают курсы. В LMS школьники учатся: смотрят вебинары и учебные материалы, решают задания и общаются с наставниками.
Благодаря такому разделению мы смогли запустить магазин не дожидаясь разработки новой LMS. Но есть и сложность — пришлось подружить новый магазин со старой LMS, которую разрабатывает чужая команда.
Часть первая: перезапуск магазина. Сентябрь 2021 - февраль 2022
Перезапуск магазина давал два ключевых профита бизнесу: корзину и прозрачную аналитику. В старом Вебиуме было серьёзное продуктовое ограничение — можно было купить только один курс в одном чеке. Хочешь второй курс — проходи всю корзину ещё раз. Это плохо и для бизнеса и для учеников. Для бизнеса — это низкий средний чек, а для учеников это ухудшает качество наших услуг — ни один ВУЗ не принимает студентов по результатам только одного предмета. Технический ВУЗ может требовать информатику и математику, фармацевтический — химию и биологию, и почти везде требуется русский язык.
Нам нужно было сделать удобную корзину: с возможностью купить сразу пачку курсов и получить за это скидку. А ещё в старой системе было плохо с аналитикой — нам нужно было сделать структуру данных, которая не вызывает желания постричься в монахи. Серьёзно — посчитать важные для бизнеса показатели вроде выручки или количества проданных месяцев на старой системе стоило несколько дней ручной работы, потому что эта информация хранилась в JSON-ах с разнородным форматом.
Архитектура
Мы сделали две системы: наш магазин отвечал за продажи курсов, а обучение мы оставили в старом монолите на Ruby. Конечно, у каждой системы мы сделали отдельную БД — если бы мы переиспользовали старую, то притащили бы все болячки аналитики.
При переносе пользователей заметили отклонение от правила Парето — мы перенесли 99% (!) пользователей и считали свою работу успешной. Но выяснилось, что 1% оставшихся пользователей делали у нас кучу заказов, и они настолько важны для бизнеса, что без них задачу нельзя было считать решённой. На перенос этого 1 золотого % у нас ушло больше времени, чем на 99% остальных!
Система, которая как-то работает, у Вебиума уже была. Перед нами стояла задача сделать систему, которую будет удобно поддерживать и развивать будущей ин-хаус команде Вебиума. Новая система получалась сложной, поэтому мы с самого начали наняли Антона Давыдова на роль архитектора. Он разобрался в деталях бизнеса и спроектировал основные составляющие системы ещё до того, как мы написали первую строчку кода, а самое главное — оставил понятную документацию, которая сильно ускоряет погружение новых программистов в работу над Вебиумом даже после нашего ухода.
У старой и новой системы 2 ключевые точки соприкосновения: пользователи и покупки. Для пользователей мы придумали однонаправленный поток данных с новым магазином в качестве источника правды. Регистрация и изменение пользователей происходит на стороне магазина. События об изменениях стримятся в старую LMS. Если студент заходит на страые страницы управления профилем в LMS — мы его редиректим на магазин.
Открытие доступов к курсам тоже работает однонаправленно: при покупке курса в новом магазине создается событие, в ответ на которое старая LMS открывает доступ студенту. Отправка событий построена на RabbitMQ.
Инфраструктура
Для маршрутизации трафика мы использовали уже проверенную на Снобе комбинацию из traefik и Express.js. Express.js выполняет роль BFF (Backend for Frontend). Это быстрый прокси-сервер, у которого несколько важных функций:
SSR (Server Side Rendering) важных с точки зрения SEO-страниц. Поисковики — важный источник трафика для Вебиума.
Управление аутентификацией. Мы вместе с аутсорс-командой старой системы спроектировали и запрограммировали систему кросс-аутентификации: вход/выход в одной системе автоматически приводит к входу/выходу в другой системе.
Маршрутизация запросов между новой и старой системой. Благодаря прокси комбинация из нового магазина и старой LMS выглядела единой системой для пользователя.
Все неизвестные пути Express.js направляет на обработку в legacy-систему. Если после запуска нужно откатиться на старую систему — мы просто отключаем прокси. Старая система продолжает работать как ни в чём не бывало.
На схеме показаны примеры 3 видов запросов:
/privacy-policy/ (желтый) — статичная страница нового магазина. Express.js обрабатывает запрос и отдает результат клиенту.
/api/v2/courses/ (синий) — запрос к новому бэкенду. Запрос проксируется в новый Python-бэкенд, результат отдается клиенту.
/certificates/ (красный) — страница из LMS. Express.js приложение ничего не знает про такой путь. Запрос проксируется в Ruby-приложение, результат отдается клиенту.
Запуск
Запуск новой большой системы – всегда риск. Особенно когда система напрямую отвечает за продажи.
Мы запускались в преддверии старта продаж Весеннего Фреша – второго по важности и прибыли курса Вебиума. Сломать флоу оплаты на старте совершенно неприемлемо — один день простоя стоит миллионы рублей. Упускать выгоду от запуска корзины удобной оплатой и классной системой скидок тоже не вариант — бизнес у Вебиума сезонный.
Поэтому мы заранее тщательно проработали план отката: заполнили все необходимые для продаж данные и в новом магазине, и в старой системе. Подготовили фиче-флаги — переменные окружения, переключение которых моментально вернёт старую систему в строй.
До Вебиума у нас был опыт большого перезапуска Сноба. Cноб нам пришлось откатывать три раза — поэтому мы подготовились по полной.
После запуска в техподдержку одно за другим стали поступать сообщения о проблемах с аутентификацией. Мы уже готовы были жать на кнопку отката, но перед этим посмотрели аналитику. Выяснилось, что с проблемами сталкивается совсем небольшой процент пользователей — в основном те, у кого в старой системе было несколько учетных записей. Вместо отката мы быстро починили самые серьёзные проблемы, а по сложным кейсам составили инструкцию для техподдержки.
Ни один пользователь не столкнулся с проблемами непосредственно во процессе оплаты – все, кто хотели заплатить Вебиуму деньги, смогли это сделать. Мы этим гордимся.
Запуск состоялся. Дальше мы чинили обнаруженные баги, доделывали оставшиеся фичи и передавали разработку магазина Никите Савостину — новому техническому директору Вебиума, которого помогли нанять параллельно с разработкой.
Часть вторая: Перезапуск LMS. Март - сентябрь 2022
После запуска магазина перед нами стояла задача перезапустить LMS – платформу, где ученики смотрят вебинары, решают задания и общаются с наставниками.
Вместе с LMS нужно было перезапустить админку для сотрудников. В магазине мы смогли обойтись стандартной Django-админкой — ей пользуются контент-менеджеры когда нужно обновить цены, создать промокод или обновить маркетинговые лендинги. Для LMS так не пойдёт — в админку заходят сотни сотрудников каждый день: распределяют учеников по группам, редактируют расписание, проверяют домашние задания и общаются с учениками.
При разработке LMS и админки мы столкнулись с новыми вызовами. С одной стороны, для LMS нужно меньше интеграций с внешними сервисами — не нужно принимать платежи и отправлять информацию в бухгалтерские сервисы или интегрироваться с легаси-системой, которую разрабатывает другая команда. С другой стороны, LMS сложнее — в магазине всё строится вокруг одного большого сценария продажи курсов ученикам, а в LMS одно перечисление сценариев занимает 7 страниц в договоре. Всего сценарии можно разделить на 5 групп:
Работа с курсами, расписанием занятий и календарём, отдельно для учеников и для наставников.
Автоматическое и ручное распределение учеников по группам.
Учебный контент: просмотр вебинаров и уроков, решение и проверка заданий.
Просмотр и рассылка уведомлений на сайте и в VK для учеников и сотрудников.
Чаты с обсуждением заданий: отдельно для учеников и наствников.
В магазине все пользователи одинаковые — это ученики. В админке LMS куча разных людей: контент-менеджеры, наставники, управляющие домашними заданиями, преподаватели предметов и супер-админы. У всех свои права и ограничения.
Архитектура
Когда мы делали магазин, мы сразу думали про новую LMS. Со стороны магазина ничего не менялось – он по прежнему складывает события в RabbitMQ и не думает, какая LMS их потребляет: новая или старая.
С точки зрения архитектуры LMS разделена на следующие элементы:
Курсы и их жизненный цикл
Распределение учеников по группам
Аутентификация и авторизация и управление ролями сотрудников
Аутентификация и авторизация учеников
Уведомления
Учебный контент
Чаты наставников и учеников
Про каждый их них можно написать отдельную статью. Покажем немного внутренней кухни: ниже схема коммуникации между ключевыми элементами LMS. Картинка взята из внутренней документации — показываем без прикрас.
Инфраструктура
Код нового Вебиума хранится в 6 репозиториях:
Бэкенд магазина
Фронтенд магазина
Бэкенд LMS и админки LMS
Фронтенд LMS
Фронтенд админки LMS
Инфраструктура
Мы полностью разделяем кодовые базы магазина и LMS. Это даёт гибкость — нам не нужно учитывать маркетинговые фичи в сущностях учебного процесса и наоборот. В старой системе такого разделения не было — это мешало быстро тестировать продуктовые гипотезы.
Мы храним бэкенд LMS и админки в одном репозитории, а фронтенды — в разных. Бэкенд общий, потому что данные довольно сильно размазаны между системами. К примеру, с точки зрения бэкенда домашнее задание, которое решает ученик и проверяет наставник — одна и та же сущность. Фронтенды отдельные, потому что с точки зрения интерфейса решение домашки и её проверка — совершенно разные сценарии. У фронтенда LMS и админки разные дизайн-системы, разные пользователи, разные требования к качеству интерфейса и оптимизации.
Для мониторинга и трекинга ошибок мы используем проверенные инструменты — Datadog и Sentry. Для CI/CD впервые использовали GitHub Actions (раньше использовали Circle) и остались довольны.
Запуск LMS
Как и в случае с магазином, нам нужно было интегрировать новую систему с существующей. На запуске магазина нужно было интегрироваться со старой LMS, на запуске LMS — с новым магазином. Конечно, второй случай — проще: обе системы мы сделали сами.
lms.webium.ru — сайт LMS. До запуска там была старая LMS, после запуска — новая.
Админка LMS.
Запасной домен, на который переедет старая LMS после запуска.
С админкой LMS всё просто — сайт просто нужно включить.
Перенос LMS для учеников мы разбили на 2 этапа. На этом этапе перенесли старую систему на отдельный домен. На основном домене LMS включили новую систему, но она редиректила все запросы на старый. С точки зрения инфраструктуры работала новая LMS, а с точки зрения продукта и пользователя — старая. Это позволило нам отловить баги инфраструктуры и старой системы, в которой местами был захардкожен адрес. Когда все баги отловили — мы просто выключили редирект и ученики попали в новую систему.
Разделение на этапы помогло нам снизить количество действий и суету на запуске. Базовые проблемы с инфраструктурой и проблемы продуктом можно было решать в разное время. Ну и конечно, мы добавили себе ментального спокойствия — когда есть возможность откатиться назад одной командой, чувствуешь себя гораздо увереннее.
Мы настолько дотошно подготовились к продакшену, что запуск получился очень спокойным. Вот такую обратную связь оставляли ученики в чатах Вебиума.
Конечно потом вылезли проблемы — где-то не хватало фич для критичных процессов, какие-то части системы падали от нагрузки. В течение месяца после запуска мы исправили все эти проблемы.
Что дальше
За год мы перезапустили всю техническую платформу Вебиума без остановки учебного процесса. Теперь у бизнеса есть возможность бодро тестировать новые продуктовые гипотезы, а по прозрачной аналитике видно, в какие из них стоит вкладываться.
За время перезапуска Вебиум с нашей помощью нанял технического директора и построил внутреннюю команду разработки. Ребята программировали платформу бок о бок с нами, теперь они будут развивать уже знакомую им систему дальше.
Сейчас у Вебиума есть не только классные руководители, маркетологи, продакты, проджекты, дизайнеры, наставники и преподаватели, но и классные программисты. А мы очень гордимся проделанной работой. И готовы двигаться дальше.
Команда
Никита Алешников, бэкенд-разработчик Андрей Бацунов, фронтенд-разработчик Алексей Богословский, фулстек-разработчик Фёдор Борщёв, технический директор Тимур Брачков, фронтенд-разработчик Михаил Бурмистров, ведущий фронтенд-разработчик, руководитель проекта Самат Галимов, технический директор Антон Давыдов, архитектор Николай Кирьянов, бэкенд-разработчик Никита Лазаренко, бэкенд-разработчик Вячеслав Набатчиков, бэкенд-разработчик Александр Нестеров, фронтенд-разработчик Ксения Сафронова, менеджер проекта Эдуард Степанов, бэкенд-разработчик Владимир Тарановский, фронтенд-разработчик Алексей Чудин, ведущий бэкенд-разработчик
Вебиум — наш заказчик: Роксана Боровик, генеральный директор Виктория Гармаш, продакт-менеджер Никита Савостин, технический директор Александр Евграфов, арт-директор Алина Тупикова, продуктовый дизайнер Богдан Пилявец, ведущий аналитик Андрей Алейников, аналитик Михаил Герун, менеджер проекта Евгений Новиков, менеджер проекта Кирилл Стариков, фронтенд-разработчик Сергей Волков, фронтенд-разработчик Елена Микиртумова, верстальщица Роман Ковалев, ведущий SEO-специалист Ольга Рокоссовская, техподдержка и тестирование Катерина Климова, техподдержка и тестирование Павел Романов, технический директор на этапе подготовки перезапуска Евгений Юрьев, разработчик legacy-системы Илья Конаныхин, разработчик legacy-системы Михаил Сахно, бэкенд-разработчик
Я выкладываю в открытом виде на гитхаб почти весь код, который пишу. Большинство — это не opensource в классическом понимании, а скорее открытая разработка, когда любой желающий может увидеть всё, что программисты обычно прячут за кучей НДА. О том, зачем мне это, я уже писал раньше. В этом посте я собрал список таких проектов, рассказав чему в них может научиться джун, пересекающийся со мной по стеку.
Монолитный бекенд школы
Стек: Django REST Framework, celery, pytest, mypy
Это Django, интегрированный с 4 платёжными системами, ОФД, сервисами транзакционнвх и маркетинговых рассылок. Внутри — лучшие практики: тысяча тестов на pytest, все известные мне плагины для flake8, CI/CD на GitHub Actions.
Большой плейбук на Ansible. Внутри — 6 сервисов: бекенды, фронтенды, БД, бекапы, Metabase. Пишет логи в papertrail, создает анонимизированные дампы БД и делает еще кучу хороших практик.
Когда в «Феде и Самате» мы начинаем новые проекты, мы используем готовые и преднастроенные репозитории — для django и nuxt. Это экономит время на настройке линтеров, добавлении библиотек вроде pytest или jest, нужных на всех проектах.
Набор образов, которые помогают удобнее поддерживать небольшой продакшен на docker swarm:
Бекапы для PostgreSQL или рандомных файлов. Служат, чтобы можно было добавлением одного сервиса настроить минимальный бекап. Работают с S3 (я советую Backblaze) и healthchecks.io для мониторинга.
periodic-docker-prune. Очищает кеш докера от неиспользуемых образов. Нужен, чтобы не кончалось место, если часто деплоите большие и разные образы.
adhoc-proxy. Помогает пробросить доступ снаружи в закрытые сети. К примеру, если у вас RabbitMQ живёт в DMZ, с его помощью можно безопасно организовать доступ к management-интерфейсу.
<robots-txt-proxy. Позволяет с помощью докера делать два файла robots.txt — чтобы ни один инстанс вашего приложения, кроме продакшена, не индексировался поисковиками.
selfmailbot
Стек: python-telegram-bot, celery
Телеграм-бот для GTD-гиков вроде меня: пересылает сообщения из телеграма на почту, чтобы не терять и разбирать всё, что вам пишут. Подробности тут.
В 2021 году в комментах к моему каналу появилось много спамеров. Я разобрал все паттерны их поведения и написал бота, который удаляет сообщения. Идея простая — если лишить спамеров возможности увести пользователя на свой канал или страницу — спалить станет не за чем. Так, у меня в канале нельзя постить ссылки на веб или телеграмм, писать не от своего имени.
Если составить топ проблем разработки, с которыми ко мне когда-либо приходили проектные менежеры, то в самом верху будет «срываются сроки». Банальное — договорились сдать в четверг, настал уже вечер пятницы, а от программиста всё ещё ни слуху, ни духу. Часто в этом виноват накопленный техдолг, плохая коммуникация или безответственные программисты. Но причина может быть и в самом менеджере, вернее в его неумении слушать.
Когда программисты называют сроки в виде вилки, типа «2–4 дня», многие менеджеры слышат только первую часть вилки — «2 дня». Если сказать «во второй половине мая», менеджер услышит «15 мая» вместо «между 15 и 30 мая». Если сказать «не меньше трёх дней», менеджер услышит «три дня».
Что с этим делать? Если вы программист — называйте абсолютные, а не относительные сроки. Не «2–4 дня», а «будет в следующий понедельник». Не «во второй половине мая», а «1 июня». Не «не меньше трёх дней», а «через три дня назову срок».
Если вы менеджер — закладывайте запасы и слушайте то, что люди говорят на самом деле, а не то, что вы хотите от них услышать.
Когда мне выпадает возможность взять новую и сложную активность вроде нового клиента или нового проекта, я пытаюсь понять: насколько я вообще хочу этим заниматься.
В какой-то момент таких активностей стало настолько много, что я в них погряз — просто брал всё, что казалось интересным и перегружался. В итоге не делал ничего важного, фокусируясь только на срочных делах, не делать которые было невозможно.
Сейчас, каждый раз, когда нужно принять решение, я провожу простой мысленный эксперимент: «Готов ли я этим заниматься всю жизнь?».
— Готов ли я всю жизнь писать код для клиента, чья доменная область у меня вызывает отвращение? Ну тогда и начинать не стоит.
— Готов ли я всю жизнь делать проект, который не принесёт денег или морального удовлетворения? Нет.
— Готов ли я всю жизнь вести бизнес, который никогда не смасштабируется? Вряд ли.
Возможно метод работает потому, что вся жизнь — это настолько серьёзный коммитмент, что мозг выключается из ежедневной тупой рутины и начинает думать глобальнее. Может ещё почему-нибудь, не знаю. Но пустых и неважных проектов у меня стало гораздо меньше.