Дизайн данных (часть 2). Как?

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

9-12 минут на прочтение
Дизайн данных (часть 2). Как?

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

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

Несколько слов вдогонку

Начну с того, что ещё немного закреплю понимание необходимости data design для обычного дизайнера/проектировщика (разумеется, если он хочет развиваться в профессии).

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

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

Процесс

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

Вводные

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

Не знаю, зачем тут мобильное приложение, но пусть будет. С регистрацией. Отлично. Погнали.

Внимательно посмотрите на изображение выше, особенно на правую его часть.

Сущности

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

В нашем случае сущности — это:

  • наш пользователь;
  • список точек (лента сообщений);
  • сама точка (она же сообщение в ленте);
  • и… мобильное приложение.

Да, для кого-то это станет новостью, но в данном контексте мобильное приложение — это тоже сущность. Оно обладает изменяемыми свойствами, эти свойства могут передаваться по API и вообще активно участвовать в жизни наблюдателя за тюленями.

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

Итого, у нас получается примерно такая диаграммка:

Операции с сущностями

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

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


Операции с пользователем:

  • регистрация (при первом открытии);
  • аутентификация (при повторном скачивании);
  • авторизация (при каждом обращении на сервер).

Для простоты пусть пользователи регистрируются по номеру телефона — тогда на стороне клиента регистрация и аутентификация будут иметь единый интерфейс и методы API: ввод номера и подтверждающего кода.


Операции со списком точек:

  • получение (когда приложение открывается или пользователь докручивает до конца списка);
  • обновление (когда появляется новая точка при открытом приложении).

Операции с точкой:

  • увеличение изображения (по клику на миниатюру).

Этого хватит. Отдельного экрана у точки не будет.


Операции с мобильным приложением:

  • открытие (ну да, открытие и есть открытие).

В итоге наша диаграммка немного расширяется:

Пока что оставим операции в покое и перейдём к самому вкусному.

Поля (свойства) сущностей

У каждой сущности есть определённые свойства. Они могут передаваться между компонентами системы (с сервера на клиент и наоборот), изменяться сами и инициировать изменение других свойств. Особенно важно понимать и фиксировать, откуда эти свойства появляются и где они хранятся. Звучит не очень понятно, да? Давайте разбираться.


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

Итого, свойства пользователя:

  • номер телефона (вводится пользователем при регистрации);
  • внутренний идентификатор пользователя в БД (ID, секретная информация);
  • глобальный идентификатор (GUID, в отличии от ID, может покидать пределы сервера);
  • секретный ключ пользователя (длинный набор символов, нужен для подтверждения того, что пользователь — это пользователь);
  • дата и время регистрации (тут всё понятно).

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


Список точек у нас состоит из, собственно, массива точек, но при этом имеет и собственные параметры. Например, параметры пагинации: мы не будем отправлять на клиент сразу все имеющиеся в БД точки (понятно, что если точек будет совсем много, они будут долго грузиться — а загрузившись, повесят девайс). Вместо этого при открытии приложения оно получит только точки за последние 20 дней (отступ 0 дней). Если пользователь доскроллит до конца списка, то на сервер улетит новый запрос, в котором уже параметр отступа будет равен 20-ти дням. Затем — 40-ка. И так далее.

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

Получается, что свойства списка точек это:

  • отступ (параметр пагинации, используемый для подгрузки списка точек);
  • область видимости (количество дней, чьи точки единовременно могут находиться в памяти устройства);

Мне тут кто-нибудь возразит что, мол, отступ — это не свойство сущности, это параметр запроса к серверу. Но мы же крутые, да? Мы сделаем отступ динамическим свойством списка и будем менять его в зависимости от качества связи и мощности устройства. Одно другому не мешает.

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

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


С самой точкой всё проще. Её структура неизменна и понятна. Единственной особенностью у нас будет необязательность наличия фотографии.

Свойства точки:

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

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


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

Итого, свойства мобильного приложения:

  • версия (самого приложения);
  • идентификатор устройства (синтетический, так как до реального не на всех устройствах можно достучаться);
  • платформа (операционная система устройства);

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


Итого, наша маленькая диаграммка начинает расти:

Кэширование

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

Как это работает? Самый простой способ — это передавать на клиент URL изображения и какой-то уникальный хэш (произвольную строку, определяющую версию изображения). URL одновременно является идентификатором, по которому приложение определяет, есть ли у него эта картинка. Если нет, она скачивается и сохраняется локально, на устройстве. В локальную базу данных записывается ee URL, полученный хэш и местоположение (хотя без последнего можно обойтись). Если картинка уже имеется на устройстве, приложение проверяет её хэш. Теперь, если хэш совпадает, то картинка берется из локального хранилища. Если не совпадает — картинка скачивается, старая удаляется, в локальной БД изменяется хэш.

Вот для наглядности схемка:

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

Структура данных

Однако, возвращаясь к нашим тюленям.

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

Ещё немного усложняем диаграммку:

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

Обработка и хранение

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


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

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

После успешной регистрации на сервере формируется секретный ключ, который будет сохранен и на сервере, и на клиенте.

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


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


Все данные точек у нас хранятся только на сервере. Клиент каждый раз запрашивает их оттуда, а приложение формирует из них список.

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


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

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


Итого, наша диаграмма обретает финальные черты:

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

Артефакты

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

Что еще будет?

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

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

Павел Шерер, продюсер IT-решений

Канал в Telegram

Раньше тут были комментарии, но я решил не плодить сущности. Есть что сказать или спросить — велкам в телеграм-канал:

Обсудить в Telegram