"Виділяємо на рефакторинг 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. «Автомобіль — вагон»: знову шукаємо 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, був прорахунок. Ми розраховували, що ця платформа збільшить швидкість розробки, але вийшло навпаки. Причина все та ж: на той час сервіс був дуже сирим. Були проблеми з програванням коротких звуків, а це основа словника. Умовно кажучи, чотири рази слово саг відтворювалося, а не п'ятий додаток зависало.
Ми писали купу обгорток, намагалися боротися. Але після релізу все ж вирішили відмовитися від 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 роки фреймворки застарівають. Слід змінювати бібліотеки, які більше не підтримуються, оновлювати поточні. Часто їх оновлення несумісне зі старим кодом.
Головна заковика роботи з техдолгом в тому, що бізнес не бачить у цьому вигоди. Рефакторинг — це завдання не на один спринт. Розробники зайняті, але їх роботу неможливо «помацати»: нові фічі в продукті не з'являються. Але в тому-то і справа, що без рефакторінгу реалізувати їх не завжди можливо. Якщо старий код не був розрахований на нову функціональність, доводиться його переписувати буквально з нуля.
Приміром, продуктовий відділ просить зробити так, щоб словник показував користувачеві три найпопулярніших перекладу кожного слова. Звучить складно, але для реалізації цієї фічі не підходила структура бази даних. Звичайно, можна було б поставити чергові «милиці», але ми вирішили провести великий рефакторинг. У підсумку переписали код майже половини словника. На цю роботу один наш програміст вже витратив 4 місяці, але ще не закінчив. При цьому у користувачів з'явилася тільки одна нова функція — сортування трьох переказів. А також покращився швидкодію і з'явилася можливість впроваджувати нові фічі, але користувачі цього не помітять.
Ми домовилися з керівництвом, щоб нам виділяли на рефакторинг 10% кожного спринту. Сумарно це виходить близько двох місяців у році. Іноді я прошу дати ще більше часу. І радий, що бізнес іде назустріч. Інакше б розвивати продукт з 7-річною архітектурою було б просто неможливо.
Ще один фактор, який необхідний для роботи над 7-річним продуктом, — це сильна команда. Ми витрачаємо багато сил і часу на пошук відповідних людей і робимо все, щоб допомогти їм адаптуватися. До речі, кістяк команди з початку проекту до цих пір працює з нами.
Я вважаю, що гарна команда — це така, яка закриває старий техдолг і не створює новий. І ми намагаємося бути такою командою.
Опубліковано: 09/07/20 @ 10:00
Розділ Різне
Рекомендуємо:
5 книг про індустрії розробки відеоігор від Михайла Зінченко, Team Lead в Wargaming
Микола Палієнко, CEO EVO (Prom.ua) — як і навіщо назавжди перевели 1000 співробітників на удаленку
Онлайн-навчання в IT: робимо це правильно
Тайм-трекінг, код рев'ю та баг-фіксинг. Що для програмістів є рутиною та як справляються з нею
Що відбувається з Payoneer: збираємо апдейти