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


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


Оказавшись в подобной ситуации, я предложил команде принцип решения, при котором мы передаём на клиент минимум данных, отображаем общий статус и можем подгружать только те частные подробности, которые интересны пользователю. Поскольку я дизайнер UI/UX, решение тоже будет лежать скорее в поле UX, а статья ограничится описательной частью – про код я вам ничего рассказать по понятным причинам не смогу. Тем не менее, этот способ может стать полезным инструментом в арсенале дизайнеров и архитекторов (ребят, отвечающих за принципиальное устройство продукта), поэтому я и решил о нём рассказать. Он помог нам, может быть, поможет и вам. 


Поехали.



image


Фантазии вместо кейсов, которые запрещено называть из-за NDA


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


Разумеется, в обоих случаях нужно иметь доступ к частностям. Если есть штрафы, то какие и за что, если стройка перевалила за половину, залиты ли полы чистовой стяжкой. Но чаще нам необходимо просто знать, как там дела в общем. И всякий раз нам очень хотелось бы получить ответ поскорее.


image

При этом точность ответа во времени нас обычно не особо тревожит. Штраф попал в базу ГИБДД вчера, а в приложении появился сегодня? Даже если штрафы есть, на их оплату обычно даётся несколько дней, плюс-минус пара минут (и даже часов) обычно погоды не делают. Посылка уже приехала на почту, а уведомление пришло только через несколько часов? Это бывает, даже оператор в почтовом отделении не сразу узнаёт о поступлениях. Фундамент здания залили вчера, а поменяли статус сегодня? Фундамент даже просохнуть не успел.


Заметим, что в перечисленных случаях состояние нашей системы завязано на несколько распределённых сервисов со своей архитектурой:


Базы данных налоговой, полиции, ГИБДД, банков.
Базы авиакомпаний, отелей, автоброкеров.
Отдельные базы подрядчиков на объекте.


image

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


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


И как развитие второго есть третий способ, о котором я и хочу поговорить.



Дерево и база состояний


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


Для начала поймём количество необходимых состояний приложения. «Возведение стен завершено на 20%» и «Возведение стен завершено на 30%» – разница, безусловно, есть, но она количественная, а не качественная. «Возведение стен завершено на 20%» и «Черновое строительство завершено, началась отделка» – то, что нужно. Собираем ключевые качественные состояния по всем аспектам системы (в нашем примере – по областям, за которые отвечают подрядчики).


Строители: Вырыли котлован. Залили бетон. Задерживаемся из-за погоды.
Юристы: Получили разрешение на подведение коммуникаций. Согласовали газ. Возникли трудности с электросетями.
Сопровождение: Создали два новых варианта отделки. Изменилась марка кирпича фасада.
Финансы: С вашей ипотекой всё хорошо. Появилась возможность рефинансирования. У вас есть задолженности….


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


image

На рисунке всего три уровня иерархии: ключевые состояния подрядчиков, затем важные для нас комбинации состояний (1А, 1В… 2А, 2В...) и сочетания комбинаций (смайлики).


Корнем дерева могут стать состояния: «Строительство началось», «Строительство движется по графику», «Строительство задерживается из-за погоды», «Первый этаж успешно возведён», «Первый этаж возведён с опережением графика»…


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


Если пользователь хочет подробностей, мы даём возможность продвинуться по ветке диалога: техника прибыла, рабочие приехали, подсобки стоят, документы в порядке, материалы подъехали, строительство началось. Это всё ещё состояния, заметим.


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


image

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


Можно организовать быстрые переходы между уровнями, этакий Advanced Mode. Скажем, в нашем примере мы могли бы добавить возможность прыгнуть на уровень «Почему так» сразу после «Как дела», минуя «А конкретнее». Замечу при этом, что на уровне «А конкретнее» мы всё ещё работаем с более подробными статусами, а не с данными – а эти статусы мы также можем держать в нашей собственной базе.



Подведём черту


Такое дерево состояний позволит клиенту получать заранее определённое состояние с бэка и вместе с тем упростит углубление в подробности. Придумать такое дерево, предусмотреть основные состояния и направить пользователя по такой модели диалога с приложением – задача для UX-дизайнера и/или аналитика.



Работа с возражениями


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


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


Важно понимать, что несколько минут при заливке фундамента роли не играют, а несколько секунд при отклике приложения –  играют.



Границы применимости, плюсы и минусы


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


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


Основной минус метода –  затратность на аналитику и разработку дерева состояний.
Нужно придумать достаточное, но не избыточное количество статусов, которые будут явно описывать состояние системы, после этого построить дерево взаимодействия человека и сервиса, где каждая отдельная ветвь будет начинаться с простого статуса и раскрываться в частности. Если добавятся параметры или функциональность, может понадобиться пересмотреть всю структуру взаимодействия.


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