Функциональная архитектура цифровых продуктов, часть 3

Про общие функции, разницу подходов, зависимости и клиент-серверную функциональную архитектуру.

7-8 минут на прочтение
Функциональная архитектура цифровых продуктов, часть 3

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

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

Общие функции

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

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

Научившись видеть, находить и формализовать «сквозную» функциональность вашего продукта, вы существенно повышаете его шансы на выживание

Выявление

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

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

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

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

Пример

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

  • Показ системных сообщений. Общая функция вывода системных сообщений в интерфейс: негативных, информационных или позитивных. Может принимать параметры (статус, текст и приоритет).
  • API. Объёмный функциональный раздел, отвечающий за обмен данными между клиентом (сайтом, мобильным приложением, голосовым помощником etc.) и сервером.
    • Отправка запроса. Функция отправки запроса на сервер. Всегда включает в себя параметры (как минимум адрес и, собственно, передаваемые данные). Может совмещаться с авторизационными действиями: очень часто тело запроса содержит в себе данные, а заголовки запроса — авторизационную информацию (подробнее об этом в дизайне данных).
    • Обработка ответа. Вызывает другие функции, в зависимости от ответа сервера (у разработчиков это называется callback-функции). Например, может использовать функцию «Показ системных сообщений».
  • Формы. Общий функциональный раздел для работы с формами.
    • Валидация полей форм. Общая функция проверки на корректность заполнения полей форм, включает в себя дочерние функции:
      • проверка на обязательное поле,
      • проверка корректности email,
      • проверка корректности номера телефона,
      • проверка корректности даты и/или времени,
      • проверка корректности чисел,
      • проверка по регулярному выражению (сама регулярка передаётся параметром).
    • Показ сообщений форм. Показ сообщений о событиях форм (сообщение отправлено, поле «email» заполнено неверно и тп). Может использовать другую общую функцию «Показ системных сообщений».
    • Данные форм. Общая функция обмена данными форм с сервером.
      • Отправка данных формы. Общая функция на клиенте (браузер, мобильное приложение), отвечающая за передачу данных формы на сервер. Может использовать функцию «API → отправка запроса».
      • Обработка ответа. В зависимости от ответа сервера, выполняет те или иные действия (показ сообщения об ошибке, перенаправление на другой экран и тп). Может использовать функцию «API → обработка ответа».
  • Наличие сети. Проверка наличия доступа к Интернету при загрузке экранов и отправке форм. Может включать в себя дочерние функции (например, для блокировки интерфейса мобильного приложения или отложенной отправки данных форм, когда появится интернет).
  • Обновление экрана. Обновление содержимого экрана мобильного приложения (например, по свайпу вниз). Всегда инициирует выполнение других функций.
  • Аналитика. Общая функция отправки данных в аналитику.
    • Переход. Отправка в аналитику перехода между экранами. Во многих SDK эта функция реализуется «из коробки».
    • Событие. Отправка в аналитику отдельного события (например, добавления товара в корзину или шэринга поста в соцсети).

Разница подходов

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

Это сделано не случайно, а в качестве иллюстрации к основному посылу всего цикла:

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

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

Зависимые функции

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

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

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

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

Выявить взаимосвязь функций — значит правильно расставить приоритеты реализации

Иерархическая зависимость

Вот смотрите. Допустим, функция отправки SMS используется нами лишь в одном месте: когда пользователь подтверждает номер телефона. Мы совершенно уверены, что больше никто и нигде не будет отправлять SMS в нашем проекте. Это значит, что отправка SMS — не общая функция, и приступить к её разработке можно будет, когда мы доберёмся до, собственно, подтверждения номера.

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

Линейная зависимость

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

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

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

Пример запрета на предоставление email при регистрации через ВКонтакте

Конечно, это всё решаемо. Поля в БД можно добавить, регистрацию переписать. Только зачем, если можно с самого начала сделать правильно? Спроектировать сразу обе, передать разработчикам и сказать, что пока нужно делать только одну?

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

Серверные и клиентские функции

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

Снова приведу пример. Вот есть у вас общая функция валидации с переданным параметром «email». Что она должна уметь делать? Проверять корректность введённых или переданных данных — и всё, вроде. Но нет.

Клиент

На клиенте (например, в браузере) она должна проверять корректность введённых данных и:

  • Если данные не валидны:
    • визуально изменять поле (изменять цвет текста и границ на красный);
    • выводить сообщение «некорректный email» рядом с полем;
    • запрещать отправку формы.
  • Если данные валидны:
    • скрывать сообщение и возвращать визуальное состояние поля (если ранее оно было заполнено неверно);
    • передавать управление функции отправки данных на сервер.

Сервер

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

  • Если данные некорректны:
    • возвращать на клиент ошибку с текстом «некорректный email» и признаком поля, рядом с которым нужно показать сообщение;
    • прерывать выполнение.
  • Если данные корректны, возвращать управление функции обработки данных форм (например, регистрации), где уже происходит очистка данных и их запись в БД.

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

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

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

Итог

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

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

Канал в Telegram

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

Обсудить в Telegram