"Выделяем на рефакторинг 10% каждого спринта". Как в EnglishDom развивают продукт с 7-летней архитектурой

Привет! Меня зовут Никита, я Team Lead IT-отдела онлайн-школы английского языка EnglishDom. В моей IT-команде 10 человек. Большинство — в офисе в Днепре и несколько человек удаленно.

Как продуктовая IT-компания мы разрабатываем экосистему программ. Кроме основной платформы — цифрового учебника ED Class, а также приложения к нему, которое помогает делать домашнее задания с мобильного, есть программа ED Words — для того чтобы учить слова. Про разработку последнего я и хочу рассказать подробнее.

Мы начали создавать ED Words 7 лет назад: сначала как веб-проект, потом перенесли в мобайл. За это время не раз меняли и дорабатывали сервис, сталкивались с последствиями принятых решений по архитектуре и отдавали техдолги.

За 7 лет словарем ED Words воспользовались 300 тыс. человек. Сейчас у нас 18,7 тыс. активных пользователей. Причем 50% из них появились после открытия бесплатного доступа во время карантина.

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

Как все начиналось

Первый сайт EnglishDom появился в 2010 году, его разработала украинская веб-студия за $7 тысяч. Там можно было учить слова, проходить простые тренировки, читать тексты и общаться по скайпу с преподавателем. Также были сервисные модули: админка, личный кабинет студента, лендинги.

Но спустя три года наш СЕО Максим Сундалов решил сделать более функциональную версию веб-платформы и собрал первую IT-команду: 3 бэкендщика, 2 фронтендщика, дизайнера и техлида. Так с конца 2013 года мы начали разрабатывать онлайн-тренажер, который должен был состоять из разных модулей: курсы, словарь, разговорник, грамматика и практика.

Также нужно было сделать веб-платформу с кабинетом студента, перенести данные пользователей со старого сайта на новый и сделать удобную админ-панель. Мы понимали, что имеем дело не с однодневным, а с большим длительным проектом, и, исходя из этих соображений, выбирали технологии. Костяк архитектуры хотели сделать качественным, модульным, расширяемым. Остановились на LEMP-стеке — в то время он хорошо подходил. Это был PHP-фреймворк ZF2 на бэкенде, админ-панель Ext JS 4 и Backbone на фронтенде. Рендеринг сайта происходил на сервере.

С продуктовой стороны мы ориентировались на популярные сервисы Duolingo и Lingualeo. Нам нравился их подход к обучению, геймификация процесса. Но в то же время хотели сделать более качественный продукт: с удобным модулем для изучения слов (чего не было в Duolingo), с адаптивной версткой под мобильные устройства (чего не хватало в Lingualeo), без багов (чем грешили оба сервиса) и полностью бесплатным.

У нас собралась сильная и хорошо укомплектованная техническая команда, все ребята с уровнем выше среднего. Поэтому с технической частью работы сложностей не возникало. Загвоздка была в другом: у нас не было ни бизнес-аналитика, ни продакт-менеджера — в 2013-м в Днепре еще вообще почти не знали о таких специалистах.

Ядро команды EnglishDom

Продуктовые решения принимали в фокус-группах. Костяк составляли СЕО, я и один из бэкендщиков. Мы активно учили английский в Lingualeo и, можно сказать, были целевой аудиторией таких продуктов. Звали присоединиться к встречам и всех желающих ребят из команды.

На встречах принимали все решения насчет того, каким будет продукт. Например, сколько давать вариантов переводов каждого слова, нужны ли иллюстрации к нему, показывать ли информацию о том, какая это часть речи. Решения давались тяжело: иногда совещались по 5-7 часов подряд. Временами так уставали, что уже не могли ничего придумать, и некоторые из нас реально спали.

От наших решений напрямую зависела реализация сервиса. К примеру, если студент изучит слово the car с переводом «машина» и позже добавит себе новый перевод «вагон», то после этого нужно обнулять прогресс по изучению слова или же считать его изученным? А если пользователь убирает слово из набора, удалять ли это слово с других наборов? Или удалять только этот перевод?

На разработку модуля для изучения слов, который трансформировался в самостоятельный сервис ED Words, мы потратили около трех месяцев. Из них два ушло на коддинг. Работали по канбану: итерациями по одной неделе. Выбирая методологию, распечатали книгу по Agile, прочитали — и на этапе разработки проекта с нуля именно канбан показался самым эффективным. К тому же у нас не было проектного менеджера или скрам-мастера, поэтому искали что-то простое в выполнении. И с методологией не прогадали.

Релиз выкатили в сентябре 2014-го. Дело в том, что мы решили выпустить сразу всю веб-платформу со всеми составляющими модулями, а на разработку всех компонентов понадобилось около года. В то время мы еще не знали понятия MVP. Казалось, что если в продукте не будет сразу всех инструментов для онлайн-обучения английскому, то пользователи не смогут воспринимать его как полноценную платформу. К тому же хотели быть лучше конкурентов и из-за этого не могли выпустить урезанную функциональность.

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

Как дорабатывали веб-версию словаря

В 2013-14 годах словарь казался нам технически сложным и нагруженным продуктом. Мы хранили базы данных с 50 тыс. самыми распространенными английскими словами, 200-300 тыс. вариантами перевода этих слов. Когда пользователь добавлял в свой словарь новое слово или словосочетание, которых у нас в базе данных не было, мы обращались к внешним сервисам: WordsAPI, Google Cloud Translation и даже офлайн-словарям. И парсили перевод, транскрипцию аудио, а также 10 разных иллюстраций к этому слову или фразе.

Одной из фич была возможность для пользователя выбирать к своему слову иллюстрацию. Человек мог выбрать любую из 10 заранее предложенных картинок. Еще была абстрактная иллюстрация, которую мы сами генерировали (я называю это «оверфичинг»). Также думали о том, чтобы разрешить юзерам загружать свою картинку, но в итоге от этого отказались. Поэтому для каждой связки «слово-перевод» искали изображения. Например, «car — машина»: ищем 10 картинок по Google Search API. «Car — вагон»: снова ищем 10 картинок и сохраняем в базу. В результате таких иллюстраций стало много.

Данные нужно было передавать буквально за секунду, иначе пользователь устанет ждать. Эту функциональность реализовали с помощью очередей. При этом необходимо было с сервера уведомить клиента (браузер) о том, что мы уже собрали все данные, чтобы показать слово. А это уже другие механики — например, EventSource, Long Polling или WS. И пока собирали данные по новому слову, следовало показать временную картинку и заглушку для пользователя.

Каждый раз, когда человек добавлял новое слово к себе в набор, мы создавали его копию. Это делалось для того, чтобы пользователь мог гибко настраивать словарь под себя. Скажем, поменять перевод любого слова или иллюстрацию к нему. Мы думали, это будет удобно.

Но минус этого подхода в том, что нужно было «содержать» все эти копии. Пока пользователей было немного, проблем не возникало. Но аудитория росла, люди активно добавляли слова. На каждое слово вроде the cat приходилось до 200 тыс. записей в базе. Таблицы разрослись до 10-15 млн строк. Картинки и аудио весили 200-250 Гб — их надо было хранить, синхронизировать и бэкапить.

В результате некоторые запросы стали медленными, пришлось их оптимизировать. Также мы по-другому настроили бэкапы (они становились слишком большими) и развертку тестового окружения (стала занимать больше времени).

Если бы мы заранее знали, что придется иметь дело с таким объемом, то выбрали бы немного другой подход — например, по-иному делили таблицы и связи. Или запретили бы пользователям менять что-то в слове. Впрочем, это продуктовое решение, а не техническое.

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

В 2016 году наняли первого продакт-менеджера, который предложил добавить больше геймификации. Так у пользователей ED Words появился помощник — робот Робби (по аналогии львенка из Lingualeo и совы из Duolingo).

Спустя полгода уже новый продакт-менеджер изучил аналитику и обратную связь от пользователей и решил, что наш интерфейс перегружен фичами: люди теряются и не понимают, что к чему. Нужно упрощать. Вырезали самые непопулярные фичи. Под раздачу попали, например, робот Робби, возможность менять иллюстрацию к слову, папка «Мой словарь», в которой были все слова пользователя из всех тематических наборов. Нам как разработчикам было жаль: ведь все работало без багов. Но мы понимали, что продукт от этого только выиграет.

Как переносили сервис из веба в мобайл

В 2016-2017 годах мы видели, что мобильный трафик догоняет десктопный: соотношение близилось к 50/50. К тому времени мобильная версия сайта уже пользовалась популярностью. Но мы понимали, что все равно это не так удобно, как мобильное приложение.

Какое-то время мы колебались: не было опыта работы с мобайлом. Останавливало, что под разработку мобильного приложения нужно собирать команду с нуля: программистов под iOS и Android, продакт-менеджеров, бизнес-аналитиков, дизайнеров, QA, которые умеют работать с мобильными интерфейсами.

Наконец, весной-летом 2017-го мы уже всерьез задумались о выходе на мобильные платформы. Анализ в Amplitude показал, что самый высокие показатели Retention как раз у словаря. Мы предлагаем интервальные тренировки, и до 90% пользователей возвращаются на следующий день, чтобы повторить выученные слова. Люди сами писали, что охотно бы перешли на мобильное приложение.

Руководство поставило задачу: придумать, как можно разработать мобильные приложения под iOS и Android минимальными ресурсами, то есть в рамках существующей команды.

После хорошего анализа и ресерча выстроили концепцию. Мобильное приложение ED Words должно стать клиентом к нашему текущему бэкенд-сервису. Так что по бэкенд-части достаточно было немного подправить API, а также доработать процедуру авторизации, добавить условия и новые заголовки.

Само приложение решили делать на React Native. Этот фреймворк тогда активно развивался, его использовали Facebook и другие крупные компании. С помощью RN можно вести разработку сразу под две платформы, будет один репозиторий и один код.

Мы уже работали с React на вебе, поэтому решили, что фронтенд-команда сможет быстро разобраться и с React Native. Дополнительно наняли только двух человек: мобильного продакт-менеджера и разработчика, у которого уже был подобный опыт. Дизайнеры и QA остались прежние, ребята сами переучились на мобильные интерфейсы.

Основную часть разработки тянул новый разработчик, остальные члены команды почти не отрывались от своих обычных задач по веб-части. По сути, для мобайла нужно было только сверстать интерфейс и подключиться к API. На это ушло 3-4 месяца.

Разработку немного затягивали нюансы React Native: три года назад он еще был сыроват. Его активно разрабатывали и выпускали новые версии, и основная часть проблем упиралась именно в это. Например, у нас баг — чтобы его устранить, стоит обновить фреймворк. Но это сделать не получается, поскольку библиотека, допустим, RN Firebase, не поддерживает новую версию. Я слышал, что в больших компаниях команды нередко тратят около месяца, чтобы обновиться на новую версию.

Но все равно выбором фреймворка остались довольны: это хороший инструмент под те задачи и ресурсы, которые нам выделили. Благодаря этому мы создали успешный продукт силами нескольких разработчиков, хотя обычно для таких проектов нужна команда из 5-7 программистов.

А вот с другим инструментом, Expo, был просчет. Мы рассчитывали, что эта платформа увеличит скорость разработки, но вышло наоборот. Причина все та же: на то время сервис был слишком сырым. Были проблемы с проигрыванием коротких звуков, а это основа словаря. Условно говоря, четыре раза слово сar воспроизводилось, а не пятый приложение зависало.

Мы писали кучу оберток, пытались бороться. Но после релиза все же решили отказаться от Expo. Затем пришлось делать большой рефакторинг — это заняло 1-2 месяца. Понесли репутационные потери: пользователи писали негативные отзывы и ставили низкие оценки ED Words в маркетах. Зато после рефакторинга все заработало как надо.

Разработка

Как выстраивали экосистему продуктов

В 2017 году мы разработали ED Class — цифровой учебник английского с интерактивными заданиями, видео, различным контентом. В нем проходят уроки английского для студента и преподавателя. Учитель видит все, что делает ученик: как водит по экрану курсором, как проходит задания, какие допускает ошибки. Также там есть чат и видео, где они могут общаться.

С технической стороны самое важное в ED Class — это полная синхронизация между учителем и студентом, как будто они сидят рядом. Мы это реализовали как peer to peer коммуникацию с помощью протокола WebSocket. Важно, чтобы задержки между двумя клиентами были минимальными.

Но у этого решения есть минусы: при плохом интернет-соединении синхронизация теряется. Сейчас делаем вторую версию движка синхронизации, где это будет исправлено. Добавим еще один промежуточный state-сервер, который будет хранить состояние сессии урока.

После разработки ED Class встала задача встроить его в экосистему продуктов компании, в том числе привязать к нему словарь.

Мы разбили задачу на 4 этапа:

1. Добавили в ED Class возможность учить слова. Контент-отдел добавил в каждый урок набор слов, и мы вывели их пользователю списком. Сделали кнопку, которая дает по четыре тренировки для каждого слова. Это никак не было связано с функциональностью текущего словаря ED Words — просто MVP, чтобы быстро проверить гипотезу. Верстка и логика этих тренировок уже была, мы повторно ее подключили.

2. Расширили функциональность, чтобы можно было учить слова со всех уроков. Мы поняли, что, когда пользователь прошел урок, у него остаются невыученные слова, и после 5-10 уроков ему приходится заходить в каждый по отдельности и доучивать по 1-2 слова. Тогда сделали общую папку: при клике на кнопку на главном экране пользователь переходит к работе со всеми словами, которые он учил в уроках. Туда же вынесли повторение.

3. Добавили связку с ED Words, видимую для пользователей. На тот момент ED Class у нас был только в вебе, мы решили дать пользователям повторять слова и с мобильного, через приложение ED Words. Когда человек учит новое слово в ED Class, оно автоматически попадает в его словарный набор в ED Words — в специальную папку «Слова из цифрового учебника».

4. Сделали копию ED Words для цифрового учебника. Дело в том, что пользователи ED Class хотели ту же функциональность для изучения слов, которая была в ED Words. Возник вопрос: скопировать код и функциональность с ED Words или попытаться сделать одну бизнес-логику по словам на два приложения. Мы выбрали нечто среднее. Провели рефакторинг и некоторые части сделали общими (например, повторение, тренировки, база слов, переводов, иллюстрации), а некоторые — отдельными (например, прогресс изучения слова у каждого продукта свой).

То есть у нас два словаря для двух приложений, но в них используются одно и то же ядро и таблицы. При добавлении слова в один из продуктов используется общая база слов. Это удобно: нам достаточно следить за качеством переводов и иллюстраций только в одном месте.

Бизнес-логика повторения и изучения слов и тренировки — те же самые. И на бэкенде, и на фронтенде мы завернули это в абстрактные скрипты, которые используют оба продукта. Но если человек выучил слово в одном из продуктов, то оно будет выучено только там. Логика прогресса разделена — в каждом продукте для этого свои таблицы.

Декомпозиция задач

Как боролись с возросшей нагрузкой

В марте этого года мы решили открыть бесплатный доступ к ED Words на время карантина. Как и следовало ожидать, это увеличило нагрузку на систему.

Напомню, что у нас клиент-серверная архитектура, и бэкенд — это большой монолит, написанный на ZF2 7 лет назад. По сути, нагрузка выросла всего лишь в два раза, но даже это стало серьезным вызовом. На тот момент у нас был один dedicated-сервер под приложение, и его реально не хватало. Мониторинг каждый день засыпал нас алертами.

Для управления логами используем сервис Graylog, для мониторинга — Grafana и Prometheus. Мы настроили хранение логов на 30 дней и стали их изучать — прежде всего нужно было выявить узкие места. Например, оказалось, что в запросах в MySQL по большим таблицам частично отсутствовали индексы и были сложные запросы с десятком JOIN.

На основе этого составили план действий по оптимизации:

  • Подключили LifeProof — инструмент для профилирования и сбора метрик PHP-кода. С помощью него обнаружили проблемные места в коде. Нашли самые медленные API и по каждой анализировали причину замедления (плохого быстродействия).
  • Обновили механизм кеширования на клиенте — перешли на HTTP ETag.
  • Стали кэшировать API, которые часто запрашиваются.
  • Перевели часть картинок, аудиозаписей и других файлов на CDN — это ускорило отдачу контента пользователям.

Но спустя неделю не увидели существенных результатов. Нагрузка все еще увеличивалась, и система работала буквально на грани. Мы посчитали, что если двигаться по прежнему плану, то нужен еще как минимум месяц, чтобы все оптимизировать. В условиях карантина такого времени у нас не было.

И мы приняли простое решение — докупить еще один сервер и настроить кластер на Docker Swarm. Как оказалось, в текущих условиях это самый правильный выход: серверы стоят не так дорого, как время команды.

На самом деле мы давно шли к оркестрации и за последний год успели подготовиться: выполняли техдолг, делали рефакторинг, пересматривали все процессы CI/CD. Но все равно команда не имела опыта работы с кластером, и мы до конца не знали, какие проблемы могут возникнуть. Например, пришлось немного повозиться с настройкой Cron на несколько серверов. Также была проблема с внешними сервисами: нужно было прописать новые сервера на их стороне. Их много, и про некоторые из них мы просто забыли. Но за 5 рабочих дней силами одного DevOps-инженера все настроили.

Вместо выводов. Как работать с продуктом «с историей»

Для разработчика идеальна ситуация, когда он каждые полтора-два года делает новый проект на новых технологиях. Тогда не нужно подстраиваться под устаревающие технологии и легаси-код. Но для бизнеса нецелесообразно каждый год заново переписывать то, что и так работает.

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

Нашему ED Words уже 7 лет. За это время мы выработали несколько подходов, которые позволяют не только поддерживать продукт в рабочем состоянии, но и регулярно добавлять новую функциональность:

  1. Так выстраивать архитектуру, чтобы ее можно было гибко масштабировать под новые фичи.
  2. Писать хороший качественный код с полным покрытием тестами.
  3. Скрупулезно вести документацию, чтобы у новых участников команды не возникало проблем с адаптацией к проекту.
  4. Регулярно закрывать технический долг.

Техдолг не может не возникать совсем: каждые 1-2 года фреймворки устаревают. Следует менять библиотеки, которые больше не поддерживаются, обновлять текущие. Часто их обновление несовместимо со старым кодом.

Главная загвоздка работы с техдолгом в том, что бизнес не видит в этом выгоду. Рефакторинг — это не задача на один спринт. Разработчики заняты, но их работу невозможно «пощупать»: новые фичи в продукте не появляются. Но в том-то и дело, что без рефакторинга реализовать их не всегда возможно. Если старый код не был рассчитан на новую функциональность, приходится его переписывать буквально с нуля.

К примеру, продуктовый отдел просит сделать так, чтобы словарь показывал пользователю три самых популярных перевода каждого слова. Звучит не сложно, но для реализации этой фичи не подходила структура базы данных. Конечно, можно было бы поставить очередные «костыли», но мы решили провести большой рефакторинг. В итоге переписали код почти половины словаря. На эту работу один наш программист уже потратил 4 месяца, но еще не закончил. При этом у пользователей появилась только одна новая функция — сортировка трех переводов. А также улучшилось быстродействие и появилось возможность внедрять новые фичи, но пользователи этого не заметят.

Мы договорились с руководством, чтобы нам выделяли на рефакторинг 10% каждого спринта. Суммарно это получается около двух месяцев в году. Иногда я прошу дать еще больше времени. И рад, что бизнес идет навстречу. Иначе бы развивать продукт с 7-летней архитектурой было бы попросту невозможно.

Еще один фактор, который необходим для работы над 7-летним продуктом, — это сильная команда. Мы тратим много сил и времени на поиск подходящих людей и делаем все, чтобы помочь им адаптироваться. Кстати, костяк команды с начала проекта до сих пор работает с нами.

Я считаю, что хорошая команда — это такая, которая закрывает старый техдолг и не создает новый. И мы стараемся быть такой командой.

Похожие статьи:
А ще пообурюємося щодо «розпилювання» 600 млн гривень Мінобороною, порадіємо за Preply, розпакуємо гаджет від Facebook і покажемо нашу першу...
Редакція DOU попросила ІТ-спеціалістів відкрити власні фотоархіви та знайти світлини, що ілюструють професійний старт. Вони також...
От редакции:В рубрике DOU Проектор все желающие могут презентовать свой продукт (как стартап, так и ламповый pet-проект). Если вам...
DOU хоче знайти і відзначити ініціативи та проєкти, які наближають перемогу, формують українську ІТ-спільноту і є двигунами...
Приглашаем вас пройти курс FullStack Developer с трудоустройством в Одессе и получить новую работу — стать FullStack Developer. PHP Academy —...
Яндекс.Метрика