Два приложения на одной платформе: почему мы не унифицировали
Операционка для сотрудников и лояльность для клиентов на React Native монорепе командой из 10 инженеров. Почему мы сначала попробовали унифицировать, выкинули после трёх спринтов, и за что в итоге заплатило разделение.
Универсальное приложение, которое пытается обслужить и сотрудников, и клиентов, в итоге не обслуживает никого. Задачи пользователя расходятся за месяц, и после этого поверхность воюет сама с собой.
Начну с признания. Мы сначала попробовали унифицированный подход.
Аргумент сделал Дима, наш лид-дизайнер, на третьей неделе мобильного трека. Он нарисовал панель вкладок с переключением роли в углу. В клиентском режиме лояльность и заказы; в режиме сотрудника задачи и база знаний. Идея чистая на доске, команде понравилось, CEO поддержал. Мы построили три спринта на едином кодбейзе на React Native с ролевой навигацией.
К концу третьего спринта получилось то, что хорошо демонстрируется за 30 секунд и непригодно через 30 минут. Клиентские потоки зарывались под операционные обвесы. Потоки сотрудника замедлялись ритейловой полировкой. Тот аккуратный переключатель ролей, что был на доске, стал точкой, куда падал каждый баг от тестировщиков. Выбросили.
Три спринта, не пустяк. На следующее утро начали заново, два приложения на общей платформе. Год спустя они везли всю операционку. Ниже почему второй дизайн оказался правильным и во что разделение реально обошлось.
Почему два, а не одно
Клиент открывает приложение, чтобы сделать что-то для себя: заказ, рецепт, бонус, гарантия. Открывает раз в неделю максимум, чаще реже. Первые три секунды важны. Структура меню его не волнует.
Сотрудник открывает приложение, чтобы сделать что-то для операции: задачи, стандарт, отметить шаг, оставить заметку, глянуть дашборд. Открывает 20 раз в день. 30-я структура меню важна. Первые три секунды нет.
Это не две роли в одном продукте. Это два продукта. Три выброшенных спринта показали это конкретно: клиентская поверхность хотела быть спокойной, операционная быстрой, и объединение визуальных языков выдавало третий, который ощущался одновременно медленным и шумным.
Второй провал унификации: дорожная карта. Унифицированное приложение заставляет каждый релиз согласовывать приоритеты двух аудиторий с разной частотой. Клиентские фичи конкурируют с операционными за одну поверхность. Операционка всегда проигрывает: клиент виднее руководству. Сотрудники в течение квартала обходят приложение через WhatsApp и стикеры, проект тихо умирает.
Что общее, что разное
Мы выпустили два приложения одной командой из 10 человек. Не «параллельно делали два приложения», а «одна платформа с двумя поверхностями». Разделение по конкретным линиям.
Кодбейз: монорепо на pnpm-workspaces с шаренными пакетами и двумя приложениями-таргетами:
packages/
identity/ логин, сессии, разрешения
domain/ клиент, заказ, точка, назначение, каталог
content/ рецепты, база знаний, спецификации линз, условия гарантии
push/ обвязка пуш-уведомлений
apps/
customer/ React Native, ритейловый тон
employee/ React Native, операционный тон
Шарилось в коде: identity, доменные модели, контент, пуши.
Разнесено в коде и в процессе: UI-компоненты, навигация, телеметрия, частота релизов. Клиентская сторона релизится, когда готова, по KPI удержания. Приложение для сотрудников релизится раз в две недели независимо от готовности, потому что операционке нужна предсказуемость.
Это скучная часть истории, и она же определяет, разделение это роскошь или рычаг. С общим каркасом и разнесёнными поверхностями мы получили эффект фокуса от двух приложений и структуру затрат ближе к одному. Замерили: шаренные пакеты это примерно 60% кодбейза по строкам и примерно 30% поверхности багов по времени разбора. Цена «двух приложений» на команде из 10 человек получилась 1.5x от одного, не 2x.
Что драйвило каждое приложение
Клиентское приложение драйвилось удержанием. Каждая фича отвечала на вопрос «откроет ли её кто-то через неделю». Уровни лояльности, бонусы, история рецептов, повторный заказ линз, гарантии. Каждая фича: маленькая причина вернуться. Эстетика и тон задавались тем, что мешает возвращающемуся клиенту почувствовать, что он заполняет форму.
Приложение для сотрудников драйвилось принятием. Самое сложное в операционном софте: момент, когда фронтлайн-сотрудник выбирает ваше приложение, а не стикер на мониторе и не WhatsApp-группу. Мы мерили долю ежедневной операционки, проходящей через приложение, и держали это как единственный KPI первого квартала. С 18% в неделю запуска до 73% к концу квартала. Цифры, на которые мы смотрели, это были не звёзды в сторе, а стикеры, которые исчезали с мониторов.
Что я угадал случайно
Признаюсь во второй раз. Решение держать одну модель identity на оба приложения в этом тексте звучит принципиально. На практике мы сделали так, потому что у нас была команда на 10 человек, и второй стек авторизации мы бы не потянули. Ограничение продиктовало архитектурный выбор, который потом оказался критическим. Когда маркетинг захотел атрибутировать возвращающегося клиента на сотрудника, который его обслуживал, соединение уже существовало. Когда саппорт захотел, чтобы сотрудник логинился клиентом для сервисного тикета, сессии это поддержали.
Вывод: маленькая команда не всегда ограничение. Иногда это та самая вещь, которая мешает сделать неправильный архитектурный выбор по неправильной причине.
Что было в понедельник после запуска
В понедельник после того, как приложение для сотрудников дотянуло до 73% принятия, Дима, который спорил за унифицированный подход на третьей неделе, на стендапе сказал: «я бы принял неверное решение. Хорошо, что выкинули». Это и был конец спора об «одном приложении» внутри компании. Архитектурное несогласие заканчивает не дизайн-док, а демо, на котором правильный ответ становится очевидно правильным для того, кто хотел неправильный.