Фёдор Борщёв

Заметки с тегом «Анонсы»

Как мы запустили мобильное приложение на веб-технологиях и нам чуть не помешал Apple

Мы с клиентом запустили стартап. Он помогает малым локальным бизнесам — кофейням, барбершопам и другим — зарабатывать по модели, которую применяют Amazon и Яндекс.

Сделали мы это с помощью технологии PWA — создали практически полноценное мобильное приложение: с офлайном, пуш-уведомлениями и иконкой на рабочем столе. В этой статье делимся, как у нас получилось успеть всё это за 6 месяцев при том, что требования постоянно менялись (стартап же!)

Что за проект

Большие цифровые бизнесы зарабатывают кучу денег на пакетах и подписках. Пакеты повышают средний чек, а подписки помогают получать регулярную прибыль и планировать бюджет. Amazon и Яндекс продают пакет-подписку сразу и на кино, и на доставку товаров. Apple давно сместил фокус с продажи отдельных песен на подписку на Apple Music. Пользователь тоже выигрывает — если вы постоянно пользуетесь сервисами Амазона, месячная подписка отбивается за неделю.

Если вы — владелец кофейни, и хотите продать постоянным клиентам подписку на капучино по утрам, есть нюанс: у Амазона и Яндекса есть миллионы долларов на программистов, а у вас, скорее всего, нет.

Тут на помощь приходит Dozo — проект, в котором собственную подписку или пакет может создать любой малый бизнес — от кофейни до барбершопа.

Вот они, подписки:

На первый взгляд, это довольно простой проект — каждый зарегистрированный бизнес создаёт свои подписки и пакеты услуг, а клиенты открывают их по QR-коду и покупают прямо в приложении.


На самом деле, в нём довольно много нюансов — барбершопу и кафе нужны разные форматы меню; у одного владельца может быть несколько бизнесов, а у одного бизнеса — несколько локаций; каждому сотруднику нужна учетная запись с возможностью считывать QR-коды клиентов, но без возможности менять настройки бизнеса; владельцу бизнеса важно видеть статистику; нужна интеграция с Google Maps, платежным провайдером и SMS API; нужно правильно учитывать налоги при приёме платежей.

Работа над проектом

Любой программист знает, как больно работать с меняющимися требованиями. В этом проекте этого было очень много. Мы вместе с клиентом составили список User Stories и обновляли его после каждой встречи. Это давало уверенность, что при пересмотре планов мы не забудем ничего важного:

На старте планировали две страны и чат между пользователем и бизнесом. Через месяц стало понятно, что для тестирования гипотез хватит и одной Польши, а чат можно вообще отложить. При этом есть другие супер-важные фичи — генерация листовок с QR-кодами, или меню, где можно узнать список услуг: без них проект не запустится. А ещё отдельная история — это уведомления. Из-за того, что Apple грозились сломать пуши, а потом передумала, задачу то переизобретали, то убирали из планов, то возвращали в работу.

Для стартапа, который только пытается нащупать своё уникальное товарное предложение, найти своё место на рынке, такие резкие перемены — нормальное положение дел.

Перед тем, как написать первую строчку кода, ведущий бэкендер проекта Лёша продумал архитектуру: провёл с клиентом сессию Event Storming и построил модель данных по контекстам. Это помогло разделить стабильные и часто меняющиеся части системы — быть гибкими, но стойкими.

Часть схемы Event Storming, архитектурного воркшопа, результат которого — схематичное описание системы, с которым согласен каждый участник


Технологии

На бэкенде использовали Python и Django, на фронтенде Vue.js — проверенный стек, с которым у нас большой опыт и экспертиза. Для инфраструктуры использовали проверенные решения: GitHub Actions, Cloudflare и Heroku, для SMS-рассылок подключили Twilio. Для мониторинга взяли Hosted Graphite — стоит дешево, настраивается быстро. Задачи менеджили в привычном нам Бейскемпе.

Прием платежей сделали с помощью Stripe Connect — готового решения для маркетплейсов. Деньги приходят владельцам локальных бизнесов, а Dozo автоматически получает комиссию. Идеально!

В Dozo два языка: польский и английский. На старте работы макеты были только а английском. Чтобы упростить жизнь клиенту, файл с текстами переводили на польский с помощью ChatGPT, а клиент проверял результат и исправлял ошибки. Еще схема с ChatGPT сильно помогла фронтендерам с версткой: примерная длина строк на обоих языках была понятна сразу. Если бы переводов было сильно больше — подумали бы интеграцию со специализированными API для локализации вроде DeepL, но в случае Dozo это оверкил.

Ещё одна вещь, которую мы делали в первый раз — интеграция с Apple и Google Wallet. Карточки товаров из Dozo можно хранить и показывать из родных интерфейсов iOS и Android без запуска Dozo, прямо как авиабилеты. Кстати, наши карточки в электронных кошельках динамические — то есть информация в них регулярно обновляется. Если в вашем проекте нужны wallet-ы — не оставляйте задачу на последние спринты, потребуется время, чтобы разобраться с кастомизацией карточек и обновлением данных.

PWA

Dozo это PWA (Progressive Web App) — сайт, который выглядит и работает как приложение. У него куча плюсов:

  • в отличие от сайтов, оно умеет открываться и работать оффлайн, без интернета;
  • его можно вытащить на рабочий стол, прямо как приложение;
  • у обычных сайтов, на экране постоянно висят кнопки «вперед, назад» и адресная строка, у PWA браузерные контролы скрыты, только наш интерфейс, как у приложений;
  • одно и то же приложение работает сразу и на iOS, и на Android;
  • не нужно проходить жёсткое ревью для каждого обновления.

Если пользовались мобильным сайтом Сбербанка или Тинькофф Банка в 2024-ом году, скорее всего вы сталкивались с этой технологией. Примеры из международного рынка — PWA приложения AliExpress, Twitter, Starbucks и Pinterest.

Невооруженным глазом PWA не отличить от мобильного приложения из Google Play или App Store:

Есть и минус — на iOS нельзя выкладывать PWA в App Store, а значит пользователям гораздо тяжелоее добавить икноку на рабочий стол.

На Android (слева) можно сделать кастомную кнопку установки PWA из браузера, мы сделали баннер наверху экрана. На iOS (справа) такой возможности нет, единственный вариант— зайти в настройки сайта и нажать Add to Home Screen:

Риски PWA мы взвешивали вместе с основателем и CTO Dozo, и решили, что в нашем случае плюсы перевешивают минусы.

Как мы попали в замес борьбы двух Голиафов

Итак, два месяца до запуска. Работа расписана по дням, время — ударно допиливать фичи и запускаться. В начале февраля Apple объявляет, что выключает PWA для европейских пользователей в следующем обновлении iOS.

На примере Spotify: слева как выглядит PWA на iOS 17.3.1, справа — на iOS 17.4 Beta 3. Слева, считай, приложение, справа — вкладка в браузере.

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

Для нас же это выглядит как сход лавины в горном походе. Сетовать на несправедливость мира глупо, нужно искать путь в обход. Есть несколько вариантов, самый очевидный — засунуть сайт в нативную обертку, типа Capacitor-а, тогда для Apple мы будем выглядеть как самое обычное приложение. Проблема в том, что времени у нас осталось в обрез, а продакшен-опыта с Capacitor у нас не было. Решили запускаться на iOS просто как сайт.

К счастью, через несколько недель после анонса отключения PWA, Apple поменяла свое решение (кажется, сыграли роль в том числе и крики разработчиков в соцсетях) и мы запустились как планировали, со всей функциональностью, но нервы это потрепало и нам и клиенту.

Финал

От подписания договора до запуска MVP прошло шесть месяцев. Первые два месяца мы вели подготовительную работу, следующие четыре — активную разработку. Основную работу с нашей стороны сделали два бэкенд-разработчика, два фронтенд-разработчика и руководитель проекта.

Если вы живете в Варшаве — скоро сможете купить подписку на стрижки в барбершоп или комбо из капучино и булочки со скидкой в кофейне через Dozo. А наша команда продолжит поддерживать и развивать продукт после запуска.

Если хотите запустить проект с нами — пишите в телеграм или оставьте заявку на нашем сайте.

Команда

Андрей Бацунов, фронтенд-разработчик,
Алексей Богословский, ведущий бэкенд-разработчик,
Артур Даценко-Боос, фронтенд-разработчик,
Полина Никитина, бэкенд-разработчица,
Иван Седов, фронтенд-разработчик,
Инна Сидорова, руководитель проекта.

Как мы перезапускали Медицинскую информационную систему

Клиники «Чайка» — это сеть премиальных клиник с отличными врачами, куда можно обратиться с любым вопросом — от сломанной ноги до зубной боли. Для организации работы, они используют собственную МИС (медицинскую информационную систему), которую пилит небольшая команда разработчиков в штате.

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

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

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

Задача и генеральная идея

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

До этого проекта, у нас был опыт «технологической трансформации» команд разработки и тогда мы зареклись делать «консалтинг» — это работа, в которой нужно 100% вовлечение технического директора и её сложно делегировать — такой бизнес мы с Федей масштабировать не умеем, а повторять одно и то же, чтобы просто заработать денег — скучно.

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

Существующая МИС решала кучу задач, автоматизируя работу клиник со всех сторон — от ведения медицинских карточек до учёта денег, заплаченных пациентом. Варианта переписать какую-то её часть, временно остановив разработку, как мы сделали в Вебиуме или Снобе, у нас не было — бизнес растёт, и открытие новой клиники в Тбилиси не будет ждать, пока мы перепишем миллион строк с TypeScript на Python.

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

К счастью, в проекте была сильная продуктовая команда: продакты с медицинским образованием и дизайнер с большим опытом работы над проектом — они были нашими основными партнерами в этой работе.

Архитектура и планы

Текущую разработку замораживать нельзя, так что новый код для бизнес-фич надо писать сбоку. Учитывая, что бекенд написан на чужом для нас стеке (TypeScript), единственное решение — это event-driven архитектура. Пусть текущая МИС будет одной из многих систем в гетерогенной среде, где программы, обслуживающие разные части бизнеса, общаются друг с другом через события. Скажем, сотрудник ресепшн завёл карточку пациента в привычном интерфейсе — система сформировала событие «пациент заведён», обогатила его всеми данными пациента, и положила это в общее информационное пространство — брокер событий. Все другие программы, которым нужна информация о пациентах, слушают, поток событий и заводят пациента у себя. Точно так же передаются данные о новых докторах, диагнозах, операциях, взаиморасчётах и всех других изменениях в системе.

Чтобы не изобретать велосипед, мы построили свою модель данных на основе готового стандарта — FHIR. Кроме прямой пользы — держать данные в едином стандарте, FHIR помогал нам погружаться в доменную область — изучая описание стандарта, легко узнать, что диагнозы надо привязывать к частям тела (диагноз «кариес», часть тела «22 зуб со стороны языка»).

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

AstraShare

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

Начинать большой проект, не проверив эти риски — глупо: можно влить кучу человекомесяцев, а в конце узнать, что проект никогда не запустится, потому что события 2% случаев просто теряются, и данные о новых пользователях не долетают до всех систем. Мы начали с маленького — решили сделать сервис, который проверял бы все риски интеграции, но не стоил как космолёт. Выбрали AstraShare. Его идея простая — когда врачи Чайки направляют данные пациента в стороннюю больницу или в страховую компанию, можно не печатать сотни страниц истории болезни, а сделать сервис, в котором эти данные можно будет получить в электронном виде, или скачать PDF и распечатать самому. А у нас останется инструмент коммуникации со всеми, кто пользовался этим сервисом и уверенность, что данные получены кем надо.

Сервис мы в итоге запустили за 5 месяцев силами команды из трёх программистов и одного архитектора. Чтобы упростить задачу команде клиента, мы пожертвовали проработкой событийной модели: вместо десятка «удобных» событий «заведён пользователь», «поставлен диагноз», «сделано наблюдение» сделали одно большое событие «медицинская запись изменена», данные из которого уже разбирали в нашей системе. Но даже такое гигантское событие доставлялось плохо — примерно в 0.5% случаев событие не генерировалось. Чтобы понять, насколько это много, представьте, что из вашей карточки в стоматологии с такой вероятностью пропадёт информация о вашей аллергии на обезболивающее.

Для решения этой проблемы, мы дали возможность администраторам переотправлять карточки в сервис. Как бы грустно для программистов это ни звучало, для бизнеса результат был отличный — они получили новый сервис и команду, которая точно знает, что может сделать, а что — нет. Чёткое «нет» от программистов — вообще очень важная штука: гораздо лучше, чем «да», которое превращается в «нет» через полгода, когда клиентам или партнерам уже обещан новый сервис.

Итак, мы убедились, что архитектура работает. Время браться за более сложные задачи! У бизнеса было три основных запроса: улучшение системы биллинга, мета-лаборатория и новый интерфейс стоматологов. Рассказ о биллинге здесь опустим — это довольно скучная (хотя и безумно сложная для новичков) штука, которую мы успешно делали уже много раз. А вот о лабораториях и интерфейсе стоматологов расскажем подробнее.

AstraLab — мета-лаборатория

Чайка работает с несколькими лабораториями, отсылая туда пациентов и забирая результаты анализа. Конечно, эту работу нужно максимально автоматизировать — чтобы клиентам не приходилось таскать справки из лабораторий, а медсёстрам — забивать в систему данные из них, попутно допуская ошибки. В идеале мы выписываем направления на анализы в своей информационной системе и через какое-то время в карточке пациента автоматом появляются результаты анализов, а врач с пациентом получают об этом уведомление.

Любая интеграция такого рода — это сложная задача: другие системы делали программисты со своим представлением о доменной области, о структурах данных и о том, что такое надёжность передачи информации. Одна лаборатория поддерживает FHIR, но с каким-то другим справочником, другая — отдаёт результаты в виде XML-файлов, которые забирает с сервера специальная софтина на винде, у третьей — свой собственный REST API с особым способом аутентификации.

К тому же сразу после запуска сложных интеграций нужно закладывать период в 1–2 недели, когда всё работает нестабильно, а программисты почти в реальном времени правят мелкие несоответствия в форматах данных или протоколах. Это довольно сложно делать на старом монолите из-за длинного релизного цикла: внести одно изменение на проде почти нереально, нужно подсаживать его в «релизный поезд» — пачку изменений, которая уходит на ручное тестирование и будет выпущена только после того, как все фичи из неё будут проверены. В таком режиме даже маленькие исправления могут ждать света дня неделю.

В новой архитектуре у нас получилось реализовать этот модуль отдельным сервисом силами 4 программистов за 6 месяцев.

AstraDental

После того, как мы убедились, что мы с бизнесом Чайки умеем говорить на одном языке, и при этом ещё и поставлять работу вовремя — настало время больших проектов.

Цель, которую мы ставили с самого начала сотрудничества — сделать удобный интерфейс для врачей. Информационные технологии не сильно поменяли рутину докторов: они как заполняли карточки в 80-х годах прошлого века, так заполняют и сейчас. Правда карточки теперь показываются на экране монитора, а подписи под ними стали электронными. Мы решили это исправить.

Дело в том, что врачи вынуждены заполнять карточки примерно так же, как они хранятся в базе данных: выбрать пациента, указать диагноз, записать текстом наблюдения. То есть удобно для программистов и неудобно для врачей. При этом уже в 60е годы был разработан эффективный формат клинических заметок — SOAP, в котором записи делятся на 4 блока: Subjective (Субъективный), Objective (Объективный), Assessment (Оценка) и Plan (План). Новый интерфейс использует эту же схему, ускоряя заполнение информации, где это возможно.

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

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

Дизайнеры Астры выстроили удобный интерфейс для докторов на основе зубной формулы:

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

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

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

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

Финал

В результате, мы запустили несколько классных сервисов, закрыли несколько горящих задач бизнеса. Но большая цель: «получить такую техническую часть проекта, такую команду, которая будет предсказуемо и быстро решать задачи бизнеса» — осталась нерешенной.

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

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

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

Вывод

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

Если вам нужна подобная помощь или просто классная разработка — пишите Самату Галимову @samatg.

Технические директора:
Фёдор Борщёв
Самат Галимов

Архитектор:
Антон Давыдов

Бекенд-программисты:
Вячеслав Набатчиков
Денис Сурков
Алексей Чудин
Эдуард Степанов
Николай Кирьянов
Даниил Мальцев
Владимир Войтенко
Анна Агаренко

Фронтенд-программисты:
Александр Нестеров
Алексей Богословский
Владимир Тарановский
Тимур Брачков
Михаил Бурмистров

Менеджеры:
Анастасия Шаркова
Ксения Сафронова
Иван Борисов
Дарья Львова

Сотрудники заказчика:
Алена Салкова
Александр Скоромнюк
Александр Климов
Валентин Раншаков
Виктор Косарев
Вячеслав Ключников
Денис Турьяница
Дмитрий Верижников
Дмитрий Зозулин
Дмитрий Афанасьев
Дмитрий Панов
Мария Чистова
Павел Власенко

Облачная платформа для Wiren Board

Мы разработали облачную платформу для управления промышленными IoT-устройствами. Делимся, как нам удалось это сделать небольшой командой в сжатые сроки и при этом не потерять в качестве. 

Наш клиент — компания Wiren Board. Они делают модульные программируемые контроллеры на базе Linux. Это маленькие компьютеры, размером как два сложенных вместе айфона. Их используют для того, чтобы управлять вещами в реальной жизни. Умные жалюзи дома, климат-контроль в дата-центрах и теплицах, холодильники в магазинах и фритюрницы в ресторанах, оборудование заводов и даже управление баней — вы удивитесь, как часто вы пользуетесь контроллерами Wiren Board, не замечая их.

Задача

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

При этом, если вы покупали какое-нибудь умное устройство в последнее лет 5-10, от часов до робота-пылесоса, то наверняка производитель предлагал вам возможность управлять своим устройством через интернет. Аналогичная облачная платформа сильно упростит жизнь клиентам Wiren Board.

Мы решили начать с интерфейса, где пользователи смогут посмотреть все принадлежащие им контроллеры — чтобы можно было проверить показания датчиков и бизнес-метрик, а в случае проблем — подключиться к устройствам по SSH; напомню, наши пользователи — это сисадмины крупных компаний. Забегая вперёд, добавлю, что инструмент оказался полезным не только сисадминам, но и IoT-энтузиастам, которые держат 1– 2 контроллера у себя дома.

Клиент, как всегда, хотел запуститься как можно скорее. Для этого мы максимально использовали готовые opensource-решения.  IoT — уже довольно развитая область и это позволило нам запуститься в рекордные 3 месяца, а ещё не тратить время и деньги на изобретение и поддержку своих собственных велосипедов.

Метрики

Для сбора данных с IoT устройств есть целый ворох протоколов и решений. Мы остановились на связке Telegraf и InfluxDB

Telegraf — это агент для сбора данных, который живёт на устройстве, собирает несчётное количество параметров системы и передаёт их к нам в облако. В итоговом решении мы получаем через него стандартный набор системных метрик вроде состояния сервисов,нагрузки на CPU и количества свободной памяти, а также данные с MQTT-шины, на которой работает системное ПО Wiren Board.

В мире IoT очень важен размер программы. Telegraf — модульная система, так что мы выключили лишнюю функциональность при компиляции и собрали 15Мб образ вместо 211Мб.

Для бэкенда облака мы выбрали базу временных рядов InfluxDB. База временных рядов — это база данных, которая хранит изменяемые во времени значения. Если привычный PostgreSQL работает со статичными таблицами, как в справочнике, то InfluxDB хранит историю изменений — как в выписке из банка есть остаток на счёте после каждой операции. Такой формат позволяет делать быстрые выборки вида «как коррелирует время ответа сервиса с загрузкой CPU между 01:30 и 01:35 5 февраля».

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

В InfluxDB мы надежно разделяем данные пользователей путем создания внутри базы изолированных организаций, а также используем встроенный API для ротации токенов как на запись для агентов, так и для доступа к данным со стороны Grafana для конечных пользователей.

Часть самых важных метрик системы, а некоторые данные с MQTT-шины мы интегрировали с бекендом, который разработали на Django и выводим самую ключевую информацию прямо на странице контроллера:

Туннели

Обычно контроллеры живут в закрытых корпоративных сетях и подсоединиться к ним извне довольно тяжело — вряд ли кто-то будет давать белый IP-адрес железке, которая управляет фритюрницей. Решение — туннели: сам контроллер подключается к нашему серверу, а мы, при необходимости, используем уже установленное соединение, чтобы достучаться до контроллера.

Самый простой механизм для этого — VPN — виртуальная частная сеть, проложенная поверх интернета. Сегодня VPN ассоциируется с доступом к инстаграму, но изначально он был разработан как раз для решения подобных задач. Нам, к сожалению, VPN не подходит. Имеющиеся решения либо тяжеловесны, либо плохо работают на территории России — Роскомнадзор режет все VPN без разбора. А ещё они требовали от нас очень много телодвижений — настроить распределение IP-адресов в сети и изолировать сети клиентов друг от друга.

Мы любим простые решения, поэтому остановились на frp. Fast reverse proxy — маленькая программа, которая очень хорошо решает одну конкретную задачу. Клиентская часть frp устанавливается на контроллер и соединяет выбранные локальные порты контроллера с сервером. Для подключения по SSH мы используем слегка доработанный веб-терминал WebSSH, который внутри сети облака соединяется с открытым портом frp через авторизационный прокси. Прокси нужен, чтобы пользователи не могли получить доступ к чужим контроллерам.

Авторизация звучит просто, а для пользователей проходит абсолютно незаметно, но на деле это одна из самых сложных частей этого проекта:

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

Схема работы frp с нашим агентом:

Итоги и планы

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

Мы уже собрали первую обратную связь от пользователей и сформировали беклог продуктовых задач. Оказалось, что клиенты Wiren Board уже давно ждали такой инструмент и остались довольны тестированием MVP.

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

В проекте участвовали:

  • Ведущий разработчик и автор этого текста — Алексей Чудин,
  • фронтендер — Тимур Брачков,
  • бекендер — Николай Кирьянов,
  • руководитель проекта — Настя Шаркова.

LMS для Школы Сильных Программистов

У Школы Сильных Программистов была небольшая LMS. Делать её решили потому, что принятый в индустрии Getсourse превращает редактирование уроков в мучение (в школе привыкли писать и редактировать в Notion), а пафосные опенсорсные LMS вроде moodle годились для чего угодно, кроме обучения взрослых людей. К тому же почти во всех курсах внедрена сложна фича, которую не найти в готовых продуктах — p2p-проверка домашки, когда одни студенты смотрят и ревьюят работу других: это экономит время экспертов и здорово повышает доходимость.

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

Решили, раз уж все гипотезы доказаны, просто переписать всё без новых фич, но с нестыдным кодом. Сформулировали вот такую задачу:

  • Нормальный код покрытый тестами
  • Хороший дизайн и UI-kit
  • Удобная мобильная версия для чтения на ходу

Старт

Для старта работ у нас было:

  1. Бекенд на Django, покрытый тестами
  2. Код текущего фронтенда LMS
  3. Большой пост с описанием функциональности

Старый код рефакторить было бесполезно, так что решили выкинуть всё и написать всё с нуля, ориентируясь на пост с постановкой задачи. Причин для такого решения было две:

  1. Писать хорошую дизайн-систему с нуля проще, чем рефакторить старую
  2. Cтарая LMS была написана на Vue 2, а в то время уже вышел Vue 3, в котором появилась поддержка Typescript, а ещё крутой туллинг в виде Vite и Pinia

С решением не ошиблись, получилось быстро и круто.

Редизайн LMS мы решили делать прямо в коде. Используя старую LMS как вайрфреймы, мы собрали все страницы внутри Storybook на мокнутых данных из фейкера.

Чтобы дизайн был консистентным и не отнимал много времени, мы взяли Tailwind — его атомарность позволяла быстро примерять разные варианты, а токенизированность упрощала создание компонентов в единой стилистике.

Материалы

Материалы для LMS пишутся в ноушене и отдаются на фронтенд через хитрое API. Бекенд нужен, чтобы контролировать доступ к материалам, кешировать их (чтобы грузилось быстрее, чем в самом ноушене) и немножечеко его менять, чтобы было просто рендерить на фронте.

На фронтенде материалы рендерятся в удобном интерфейсе: пригодном для чтения, с хорошо натсроенным шрифтом и с тёмной темой, Таким образом редакторы пользуются удобным и привычным текстовым редактором, а ученики читают материалы в интерфейсе школы.

Забегая вперёд: такое решение — единственная сложная часть LMS. Реализации рендеринга на Vue печальные: одна очень плохо написана, а другая редко обновляется. Сейчас мы форкнули удачную реализацию и обновляем её сами (к счастью, ноушен ничего не меняет, поэтому это не много работы).

Почему просто не использовать маркдаун? Маркдаун нужно уметь писать, а курсы в школе пишут не только программисты. Кроме того, наладить простое (git — это не простое) совместное редактировние текстов в маркдауне — тяжёлая задача.

Домашки

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

Ответы на домашку ученики пишут в удобном WYSIWYG редакторе. Редактор собран на основе tiptap. Чтобы оживить общение между студентами, мы добавили возможность оставлять реакции.

Профиль студента

По окончании каждого курса школа выдаёт кайфовый диплом: можно положить в LinkedIn, добавить в резюме или просто повесить на стенку. Чтобы в диплом было вписано корректное имя пользователя, мы сделали раздел «профиль», где студенты заполняют свои данные На странице настроек ученик управлет своими данными, а также указывает информацию, которая появится на дипломе.

К моменту работы над этой страницей уже скопилось достаточно полей, которым нужна была валидация. Делать хорошую валидацию сложно и долго, а делать среднюю — просто долго. Хотелось найти вариант, над которым не придётся думать, но при этом он будет точно понятен пользователям.

На бекенде уже была сделана хорошая валидация с понятными текстами ошибки. Чтобы забыть про валидацию на фронтенде раз и навсегда ответы с бекенда в выводятся в тосты. Перехват ошибок делается на уровне axios и все ошибки ловятся автоматически, без использования try … catch.

Релиз и что дальше

От старта до готовности к релизу прошло три месяца. Чтобы не релизить в праздники, мы решили потратить декабрь на дополнительные фичи, а после нового года выкатили её для всех учеников.

Релиз прошел плавно. В том, что так будет, мы были уверены — код был хорошо покрыт тестами.

Дополнительно убедиться в том, что новые фичи не развалили все прямо перед релизом, помогали тесты на визуальный регресс. Они сравнивают скриншоты актуального интерфейса с тем, как он выглядит в мастере, и показывают различия если что-то поменялось. Чтобы написать такой тест, нужно добавить историю в Storybook и написать тест который ходит туда через Playwright, делает скриншот и сравнивает его. Через такой такой тест не пройдут ошибки, затрагивающие рендер, в том числе ошибки на уровне CSS.

Код проекта открытый. Посмотреть можно на гитхабе.

Технологии: vue3, pinia, vitest, playwright.

Разработчик и автор статьи: Тимур Брачков.

Перезапуск Вебиума

Вебиум — онлайн-школа для подготовки к ЕГЭ. 30 тысяч школьников, тысяча наставников, 20 тысяч вопросов и 2 тысячи домашних заданий на сотни тем.

В 2021 году у Вебиума уже была рабочая система на Ruby, которую разрабатывали подрядчики-аутcорсеры. К нам они обратились с привычной проблемой — подрядчики медленно пилят фичи. Посмотрев код и оценив возможности команды, мы поняли, что рефакторить существующий код — долго и дорого, и решили перезапустить систему своими руками на привычном стеке — Django и Nuxt.js. Справились за год: с сентября 2022 все ученики покупают и проходят курсы в новой системе.

Перед нами стояли 3 задачи:

  1. Сделать так, чтобы фичи, которые хочет бизнес, разрабатывались быстрее и более предсказуемо. Обострённый пример: «давайте добавим вот эту маленькую штуку; конечно, будет завтра (возвращаются через две недели), ой нет, это займёт полгода» (и это ещё хорошо, если вернутся с таким честным ответом).
  2. Нанять технического директора и создать внутреннюю команду разработки, чтобы в будущем не зависеть от аутсорса.
  3. Сделать всё это «на лету», без остановки образовательного процесса и потерь для бизнеса.
Главная страница Вебиума — онлайн-школы, которую ждал нескучный перезапуск

Дедлайн всего проекта — сентябрь 2022. Министерство образования не станет переносить ЕГЭ из-за того, что мы не успели доделать, например, отправку уведомлений в VK.

Чтобы не подписываться сразу на гигантский годовой проект, мы разбили работу на 2 больших этапа — запуск нового магазина и запуск новой платформы обучения (Learning Management System, LMS). Магазин – часть сайта, где школьники и родители покупают курсы. В LMS школьники учатся: смотрят вебинары и учебные материалы, решают задания и общаются с наставниками.

Благодаря такому разделению мы смогли запустить магазин не дожидаясь разработки новой LMS. Но есть и сложность — пришлось подружить новый магазин со старой LMS, которую разрабатывает чужая команда.

Часть первая: перезапуск магазина. Сентябрь 2021 - февраль 2022

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

Нам нужно было сделать удобную корзину: с возможностью купить сразу пачку курсов и получить за это скидку. А ещё в старой системе было плохо с аналитикой — нам нужно было сделать структуру данных, которая не вызывает желания постричься в монахи. Серьёзно — посчитать важные для бизнеса показатели вроде выручки или количества проданных месяцев на старой системе стоило несколько дней ручной работы, потому что эта информация хранилась в JSON-ах с разнородным форматом.

Вот так выглядит корзина в новом магазине

Архитектура

Мы сделали две системы: наш магазин отвечал за продажи курсов, а обучение мы оставили в старом монолите на Ruby. Конечно, у каждой системы мы сделали отдельную БД — если бы мы переиспользовали старую, то притащили бы все болячки аналитики.

При переносе пользователей заметили отклонение от правила Парето — мы перенесли 99% (!) пользователей и считали свою работу успешной. Но выяснилось, что 1% оставшихся пользователей делали у нас кучу заказов, и они настолько важны для бизнеса, что без них задачу нельзя было считать решённой. На перенос этого 1 золотого % у нас ушло больше времени, чем на 99% остальных!

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

Кусочек функциональной схема добавления товара в корзину с учетом скидок. Нужно учесть много edge-кейсов — вся схема в 5 раз больше.

У старой и новой системы 2 ключевые точки соприкосновения: пользователи и покупки. Для пользователей мы придумали однонаправленный поток данных с новым магазином в качестве источника правды. Регистрация и изменение пользователей происходит на стороне магазина. События об изменениях стримятся в старую LMS. Если студент заходит на страые страницы управления профилем в LMS — мы его редиректим на магазин.

Открытие доступов к курсам тоже работает однонаправленно:  при покупке курса в новом магазине создается событие, в ответ на которое старая LMS открывает доступ студенту. Отправка событий построена на RabbitMQ.

Инфраструктура

Для маршрутизации трафика мы использовали уже проверенную на Снобе комбинацию из traefik и Express.js. Express.js выполняет роль BFF (Backend for Frontend). Это быстрый прокси-сервер, у которого несколько важных функций:

  1. SSR (Server Side Rendering) важных с точки зрения SEO-страниц. Поисковики — важный источник трафика для Вебиума.
  2. Управление аутентификацией. Мы вместе с аутсорс-командой старой системы спроектировали и запрограммировали систему кросс-аутентификации: вход/выход в одной системе автоматически приводит к входу/выходу в другой системе.
  3. Маршрутизация запросов между новой и старой системой. Благодаря прокси комбинация из нового магазина и старой LMS выглядела единой системой для пользователя.

Все неизвестные пути Express.js направляет на обработку в legacy-систему. Если после запуска нужно откатиться на старую систему — мы просто отключаем прокси. Старая система продолжает работать как ни в чём не бывало.

Схема маршрутизации трафика с помощью прокси на Express.js

На схеме показаны примеры 3 видов запросов:

  1. /privacy-policy/ (желтый) — статичная страница нового магазина. Express.js обрабатывает запрос и отдает результат клиенту.
  2. /api/v2/courses/ (синий) — запрос к новому бэкенду. Запрос проксируется в новый Python-бэкенд, результат отдается клиенту.
  3. /certificates/ (красный) — страница из LMS. Express.js приложение ничего не знает про такой путь. Запрос проксируется в Ruby-приложение, результат отдается клиенту.

Запуск

Запуск новой большой системы – всегда риск. Особенно когда система напрямую отвечает за продажи.

Мы запускались в преддверии старта продаж Весеннего Фреша – второго по важности и прибыли курса Вебиума. Сломать флоу оплаты на старте совершенно неприемлемо — один день простоя стоит миллионы рублей. Упускать выгоду от запуска корзины удобной оплатой и классной системой скидок тоже не вариант — бизнес у Вебиума сезонный.

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

До Вебиума у нас был опыт большого перезапуска Сноба. Cноб нам пришлось откатывать три раза — поэтому мы подготовились по полной.

Чеклист запуска. Мы подготавливаем чеклисты к каждому запуску нового продукта.

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

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

Запуск состоялся. Дальше мы чинили обнаруженные баги, доделывали оставшиеся фичи и передавали разработку магазина Никите Савостину — новому техническому директору Вебиума, которого помогли нанять параллельно с разработкой.

Часть вторая: Перезапуск LMS. Март - сентябрь 2022

После запуска магазина перед нами стояла задача перезапустить LMS – платформу, где ученики смотрят вебинары, решают задания и общаются с наставниками.

Вместе с LMS нужно было перезапустить админку для сотрудников. В магазине мы смогли обойтись стандартной Django-админкой — ей пользуются контент-менеджеры когда нужно обновить цены, создать промокод или обновить маркетинговые лендинги. Для LMS так не пойдёт — в админку заходят сотни сотрудников каждый день: распределяют учеников по группам, редактируют расписание, проверяют домашние задания и общаются с учениками.

Интерфейс общения учеников и наставников — у каждого решенного задания своё обсуждение

При разработке LMS и админки мы столкнулись с новыми вызовами. С одной стороны, для LMS нужно меньше интеграций с внешними сервисами — не нужно принимать платежи и отправлять информацию в бухгалтерские сервисы или интегрироваться с легаси-системой, которую разрабатывает другая команда. С другой стороны, LMS сложнее — в магазине всё строится вокруг одного большого сценария продажи курсов ученикам, а в LMS одно перечисление сценариев занимает 7 страниц в договоре. Всего сценарии можно разделить на 5 групп:

  1. Работа с курсами, расписанием занятий и календарём, отдельно для учеников и для наставников.
  2. Автоматическое и ручное распределение учеников по группам.
  3. Учебный контент: просмотр вебинаров и уроков, решение и проверка заданий.
  4. Просмотр и рассылка уведомлений на сайте и в VK для учеников и сотрудников.
  5. Чаты с обсуждением заданий: отдельно для учеников и наствников.

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

Так выглядит страница курса у ученика

Архитектура

Когда мы делали магазин, мы сразу думали про новую LMS. Со стороны магазина ничего не менялось – он по прежнему складывает события в RabbitMQ и не думает, какая LMS их потребляет: новая или старая.

Высокоуровневая архитектура нового Вебиума из архитектурной документации, которую мы передали вместе с проектом

С точки зрения архитектуры LMS разделена на следующие элементы:

  1. Курсы и их жизненный цикл
  2. Распределение учеников по группам
  3. Аутентификация и авторизация и управление ролями сотрудников
  4. Аутентификация и авторизация учеников
  5. Уведомления
  6. Учебный контент
  7. Чаты наставников и учеников

Про каждый их них можно написать отдельную статью. Покажем немного внутренней кухни: ниже схема коммуникации между ключевыми элементами LMS. Картинка взята из внутренней документации — показываем без прикрас.

Коммуникация между ключевыми элементами LMS. Стрелки в левой части ведут из магазина.

Инфраструктура

Код нового Вебиума хранится в 6 репозиториях:

  1. Бэкенд магазина
  2. Фронтенд магазина
  3. Бэкенд LMS и админки LMS
  4. Фронтенд LMS
  5. Фронтенд админки LMS
  6. Инфраструктура

Мы полностью разделяем кодовые базы магазина и LMS. Это даёт гибкость — нам не нужно учитывать маркетинговые фичи в сущностях учебного процесса и наоборот. В старой системе такого разделения не было — это мешало быстро тестировать продуктовые гипотезы.

Мы храним бэкенд LMS и админки в одном репозитории, а фронтенды — в разных. Бэкенд общий, потому что данные довольно сильно размазаны между системами. К примеру, с точки зрения бэкенда домашнее задание, которое решает ученик и проверяет наставник — одна и та же сущность. Фронтенды отдельные, потому что с точки зрения интерфейса решение домашки и её проверка — совершенно разные сценарии. У фронтенда LMS и админки разные дизайн-системы, разные пользователи, разные требования к качеству интерфейса и оптимизации.

Для мониторинга  и трекинга ошибок мы используем проверенные инструменты — Datadog и Sentry. Для CI/CD впервые использовали GitHub Actions (раньше использовали Circle) и остались довольны.

Мониторинг в Datadog

Запуск  LMS

Как и в случае с магазином, нам нужно было интегрировать новую систему с существующей. На запуске магазина нужно было интегрироваться со старой LMS, на запуске LMS —  с новым магазином. Конечно, второй случай — проще: обе системы мы сделали сами.

В итоге систему запускали частями

  1. webium.ru — сайт магазина (уже был)
  2. lms.webium.ru — сайт LMS. До запуска там была старая LMS, после запуска — новая.
  3. Админка LMS.
  4. Запасной домен, на который переедет старая LMS после запуска.

С админкой LMS всё просто — сайт просто нужно включить.

Перенос LMS для учеников мы разбили на 2 этапа. На этом этапе перенесли старую систему на отдельный домен. На основном домене LMS включили новую систему, но она редиректила все запросы на старый. С точки зрения инфраструктуры работала новая LMS, а с точки зрения продукта и пользователя — старая. Это позволило нам отловить баги инфраструктуры и старой системы, в которой местами был захардкожен адрес. Когда все баги отловили — мы просто выключили редирект и ученики попали в новую систему.

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

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

Обратная связь от учеников в день запуска

Конечно потом вылезли проблемы — где-то не хватало фич для критичных процессов, какие-то части системы падали от нагрузки. В течение месяца после запуска мы исправили все эти проблемы.

Что дальше

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

За время перезапуска Вебиум с нашей помощью нанял технического директора и построил внутреннюю команду разработки. Ребята программировали платформу бок о бок с нами, теперь они будут развивать уже знакомую им систему дальше.

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

Команда

Никита Алешников, бэкенд-разработчик
Андрей Бацунов, фронтенд-разработчик
Алексей Богословский, фулстек-разработчик
Фёдор Борщёв, технический директор
Тимур Брачков, фронтенд-разработчик
Михаил Бурмистров, ведущий фронтенд-разработчик, руководитель проекта
Самат Галимов, технический директор
Антон Давыдов, архитектор
Николай Кирьянов, бэкенд-разработчик
Никита Лазаренко, бэкенд-разработчик
Вячеслав Набатчиков, бэкенд-разработчик
Александр Нестеров, фронтенд-разработчик
Ксения Сафронова, менеджер проекта
Эдуард Степанов, бэкенд-разработчик
Владимир Тарановский, фронтенд-разработчик
Алексей Чудин, ведущий бэкенд-разработчик

Вебиум — наш заказчик:
Роксана Боровик, генеральный директор
Виктория Гармаш, продакт-менеджер
Никита Савостин, технический директор
Александр Евграфов, арт-директор
Алина Тупикова, продуктовый дизайнер
Богдан Пилявец, ведущий аналитик
Андрей Алейников, аналитик
Михаил Герун, менеджер проекта
Евгений Новиков, менеджер проекта
Кирилл Стариков, фронтенд-разработчик
Сергей Волков, фронтенд-разработчик
Елена Микиртумова, верстальщица
Роман Ковалев, ведущий SEO-специалист
Ольга Рокоссовская, техподдержка и тестирование
Катерина Климова, техподдержка и тестирование
Павел Романов, технический директор на этапе подготовки перезапуска
Евгений Юрьев, разработчик legacy-системы
Илья Конаныхин, разработчик legacy-системы
Михаил Сахно, бэкенд-разработчик


Автор статьи: Михаил Бурмистров

Старее