Разработка архитектуры проекта, корабли и JavaScript

в 9:25, , рубрики: javascript, Анализ и проектирование систем, архитектура приложений, Блог компании JUG.ru Group, управление разработкой

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

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

Разработка архитектуры проекта, корабли и JavaScript - 1

В основе статьи — доклад Алексея Богачука (solution-архитектора компании EPAM) с конференции HolyJS 2018 Piter. Под катом — видео и расшифровка доклада.

Подходы к построению архитектуры и роли архитектора проекта

Плавали — знаем

Так говорили моряки со шведского корабля Vasa. Вот только плавали они, спасаясь с тонущего корабля, который только что спустили на воду со стапелей. При чем тут Vasa?

Разработка архитектуры проекта, корабли и JavaScript - 2

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

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

  • корабль должен быть самым большим в Балтийском флоте: 70 метров в длину, 10 в ширину;
  • нужно три палубы, на которых разместится 300 солдат;
  • у него должно быть 64 пушки на борту в двух рядах;
  • на постройку отводится 3 года.

Разработка архитектуры проекта, корабли и JavaScript - 3

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

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

Налицо следующие архитектурные просчеты Хенрика (которые, кстати говоря, могли бы стоить ему жизни, если бы он дожил до суда):

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

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

Влиятельные друзья

Сколько приложений с неверной архитектурой мертвы еще до написания первой строки кода?
Цикл влияния архитектуры на проект выглядит следующим образом:

Разработка архитектуры проекта, корабли и JavaScript - 4

Слева направо:

  1. Есть заинтересованные стороны, или stakeholders (в случае с кораблем это король и казначейство).
  2. У них есть свои цели (первый корабль в Европе).
  3. Цели диктуют требования (конкретные характеристики будущего корабля).
  4. Далее составляются чертежи, схемы, проект.
  5. Постройка по проекту.

Ошибка на одном из этих этапов может перечеркнуть будущее вашего проекта.

Настольная книга solution-архитектора

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

Если бы мы жили во времена нашего героя, когда ошибки в архитектуре карались смертью, эта книга была бы написана кровью.

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

История заводного Джимми

Требования клиента

  • Заменить текущий UI-solution.
  • Внедрить новый подход к разработке и внедрению этого solution.
  • Нужен лучший User Experience.
  • При этом следовать всем лучшим практикам.
  • Поддержка различных платформ.

Что было сделано

Требования очень общие, нет конкретики. Не понятно, что с этим всем нужно делать. При этом команда разработчиков находится в Минске, а заказчик в Монреале. По причине того, что между Беларусью и Канадой были санкции, работа непосредственно с Канадой осуществляться не могла. Было решено работать с заказчиком через офис в Дублине. Из-за всех этих проволочек во времени и посредничества, с заказчиком не удавалось связаться и выяснить окончательно требования, сделать предложения по реализации.

Разработка архитектуры проекта, корабли и JavaScript - 5

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

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

Вывод

Любая архитектура начинается с идентификации заинтересованных сторон (stakeholders). Для того чтобы их идентифицировать, существует много подходов, мы рассмотрим один из них — построим RACI-матрицу.

RACI

Аббревиатура расшифровывается как: R — responsible, те, кто будет реализовывать; A — accountable, принимающие решения; C — consulted (люди бизнеса), консультирующие; I — informed, люди, которые должны быть информированными. Каждое из заинтересованных лиц надо отнести к той или иной категории. В матрице указываются роли и задачи.

Разработка архитектуры проекта, корабли и JavaScript - 6

Построив такую матрицу, можно понять, кто является stakeholder’ами.
Кроме того, было замечено, что среди stakeholders есть люди, которые дают ложные требования, уводящие проект в сторону. В данном случае оказалось, что это были представители других вендоров, которые были не заинтересованы в проекте. Но RACI матрица не умеет отличать таких заказчиков, для этого есть Onion-подход.

Onion

«Луковый» подход несколько отличается от RACI-матриц.

Разработка архитектуры проекта, корабли и JavaScript - 7

Суть его в том, что вокруг системы строятся слои, внутри которых указываются те или иные лица заказчика. В данном примере с самой системой будут общаться разработчики, DevOps, контент-менеджеры. Чуть выше в абстракции — люди от бизнеса. Также есть внешние регуляторы: медиа, законы и т.п. Например, для релиза приложения в некоторых странах вы обязаны пройти аудит, который проводит сторонняя компания, он выявит accessibility и другие требуемые от проекта качества, это требования внешних stakeholder’ов.

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

История: недостаточно быстрый

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

Требования клиента

  • Быть быстрее конкурентов

  • Нужно сделать так, чтобы транзакция проходила не более чем за 0,5 секунды.

Что сделали

Проект был сделан, но оказался неуспешным. Почему? Опять же, требования были не совсем корректны. Цель была не в том, чтобы сделать транзакции скоростью 0,5 секунды, а в том, чтобы сделать их более быстрыми, чем у конкурента. В итоге скорость была доведена до 0,5 секунды, но конкурент в это время достиг показателя в 0,4 секунды. Видим ошибку в определении бизнес-цели. Зачем заказчику система?

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

Разработка архитектуры проекта, корабли и JavaScript - 8

Вывод

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

Полезные для себя рекомендации вы можете почерпнуть в книге «Discovering Requirements», авторы — Ian Alexander, Ljerka Beus-Dukic.

Разработка архитектуры проекта, корабли и JavaScript - 9

История о том, как пионеры коня доили

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

Требования клиента

  • Нужно в дополнение к сайту сделать мобильное приложение, которым сотрудники будут пользоваться на своих телефонах
  • У приложения должен быть офлайн-режим.

Что сделали

Исходя из требований было принято решение писать на React Native. Разработка началась. Во время первого созвона были получены уточнения и дополнения к требованиям:

  • Сотрудникам телефоны выдает компания, и все они работают на Android.
  • Заказчика интересует только оффлайн-режим.
  • Дедлайн — два месяца.

Очевидно, что задача разобраться с уже готовым сторонним продуктом со сложной бизнес-логикой и написать новый за два месяца не укладывается в такие сроки. Было решено использовать PWA (Progressive Web Apps).

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

Разработка архитектуры проекта, корабли и JavaScript - 10

Итак, проблемы были с требованиями, с которыми не все так просто. Рассмотрим пример, какими бывают требования:

Разработка архитектуры проекта, корабли и JavaScript - 11

Есть форма, и нам нужно выдавать валидационные ошибки, если вводится неправильный URL, нужно отображать страницу 404. Что не так с требованиями? Они рассказывают о том, что должна делать система. Но для архитектора важнее то, какой система должна быть. Если углубиться в то, что должна делать система, можно слишком глубоко погрузиться в детали и уйти не в ту сторону. Такие требования называются функциональными, еще их называют MoSCoW-требованиями. Слова, которые зачастую есть в этих требованиях:

Разработка архитектуры проекта, корабли и JavaScript - 12

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

Вывод

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

История белой вороны

Требования клиента

  • Разработать отдельный сервис, конвертирующий и кэширующий данные в xml-формате.
  • Делать это он должен из сторонней legacy-системы.

Что сделали

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

Разработка архитектуры проекта, корабли и JavaScript - 13

Очевидно, что Node.js здесь белая ворона, несмотря на то, что все работало хорошо.

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

  • Технические
  • Время и бюджет
  • Стек разработчиков заказчика

Вывод

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

Далее переходим к атрибутам качества, у нас есть особый интерес к ним.

Атрибуты качества

Очень безопасная библиотека

Атрибутов качества очень много, стоит только посмотреть на список, который дает нам «Википедия».

Разработка архитектуры проекта, корабли и JavaScript - 14

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

Телефон в лесу

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

Разработка архитектуры проекта, корабли и JavaScript - 15

Именно такие сценарии использования и должен собирать архитектор, чтобы на выходе построить качественную систему. Когда у нас есть список бизнес-требований, quality-атрибутов, ограничений, мы узнали всех stakeholder’ов, мы начинаем адресовывать их в схемах-диаграммах. Эта адресация атрибутов на схемах называется архитектурной тактикой. Какие можно применить архитектурные тактики по отношению к системе с телефоном в лесу на основе имеющихся сценариев?

  • Улучшить UX, чтобы человеку казалось, что производительность выше.
  • Оптимизировать ресурсы (JS, CSS, images, fonts и др.).
  • Производить кэширование.
  • Добавить service-workers, critical path.
  • Применить компрессию.
  • HTTP/2.

Однако в случае с телефоном в лесу UX и critical path нам сразу не подходят. И снова повторим: тактики должны быть продиктованы сценариями. В этом заключается работа архитектора, выбрать из всех тактик нужные в конкретном случае. Но приложение — это не только фронтенд. На производительность также влияют DNS, бэкенд, базы данных, и все это тоже можно оптимизировать. Есть множество тактик, как это сделать, но, опять же, применение той или иной зависит от сценария использования.

Попробуем использовать паттерн CQRS (common query responsibility segregation). Применение этого паттерна в качестве тактики затрагивает даже несколько слоев приложения: как back-end, так и front-end.

Разработка архитектуры проекта, корабли и JavaScript - 16

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

Разработка архитектуры проекта, корабли и JavaScript - 17

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

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

Стойкий оловянный солдатик

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

Для того чтобы приложение работало стабильно, может быть применен принцип fail fast:

  • Всегда верифицируйте интеграционные точки заранее.
  • Избегайте медленных соединений.
  • Проверяйте введенные данные.

Зачем это использовать, рассмотрим на примере Lie Connection (лживого соединения). Это такое соединение, которое, может, что-то и пингует, но на деле не работает. Такое может происходить между клиентом и сервером, базой данных и сервером, между сервисами. Применим к нему паттерн Circuit Breaker.

Разработка архитектуры проекта, корабли и JavaScript - 18

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

Подводная лодка

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

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

Дырявое ведро

Стратегия, подобная описанной выше, применяется в паттерне «Дырявое ведро».

Разработка архитектуры проекта, корабли и JavaScript - 19

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

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

Разработка архитектуры проекта, корабли и JavaScript - 20

Что означают зеленые стрелки? Разные сервисы взаимодействуют друг с другом посредством различных протоколов, баз данных, других сервисов.

Для тех, кто хочет больше узнать о паттернах отказоустойчивости, будет полезна книга «Patterns for fault tolerant software», автор — Robert S. Hanmer.

Разработка архитектуры проекта, корабли и JavaScript - 21

Также рекомендуем книгу «Software Architecture in Practice», авторы — Len Bass, Paul Clements, Rick Kazman. В ней вы узнаете о других атрибутах качества и тактиках следования им.

Разработка архитектуры проекта, корабли и JavaScript - 22

Пример на десерт

Кейс

  • Мы хотим предложить существующему клиенту улучшения для текущей платформы.
  • На этой платформе написано уже около 400 статических сайтов.
  • Проблема в том, что это дорого и долго.
  • Контент-менеджмент тоже дорогой и долгий.
  • Платформа написана на Drupal.

Допустим, для решения этих задач можно использовать следующие фреймворки:

Разработка архитектуры проекта, корабли и JavaScript - 23

Мы уже нашли заинтересованных лиц. Нужно определить цели.

Самая глобальная цель — удовлетворить заказчика.

Бизнес-цель — оптимизировать затраты заказчика (это уже интересная заказчику цель).
Далее идут более частные цели: уменьшить время вывода продукта на рынок, сократить время контент-менеджмента.

Затем обнаруживаются следующие ограничения:

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

Определив quality-атрибуты, мы выяснили:

  • Важно оставить возможность поддержки всех 400 сайтов (maintainability).
  • Нужно внедрить тесты, чтобы не сломать существующий функционал (testability).
  • Переиспользовать весь существующий контент (re-usability).
  • Работать должно быстро (performance).
  • Доступность для некоторых типов сайтов (accessibility).

Когда мы строим архитектуру, нужно следовать определенной модели. Рассмотрим модель C4. Строятся несколько диаграмм.

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

Разработка архитектуры проекта, корабли и JavaScript - 24

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

Переходим к контейнерной диаграмме:

Разработка архитектуры проекта, корабли и JavaScript - 25

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

Компонентная диаграмма выглядит так:

Разработка архитектуры проекта, корабли и JavaScript - 26

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

Как ускорить рабочий поток контент-менеджмента? Нужно предоставить возможность контент-менеджерам работать непосредственно с модулем генерации сайтов. Помня все предыдущие практики, мы добавляем необходимые подходы к модулю Template Service. Отметим, Drupal используется сугубо в контент-хабе. Приходим к выводу, что для статической генерации подойдут фреймворки NuxtJS или Next.js, которые написаны, соответственно, на Vue.js и React.js. В качестве замены интеграционным точкам выгодно использовать GitHub и ветки, таким образом, мы приходим к JAM-подходу. Расшифровывается как JavaScript, API, Markdown.

Разработка архитектуры проекта, корабли и JavaScript - 27

Видно, что стек, который было решено использовать, отличается от первоначального. С одной стороны мы использовали Node.js и какой-то современный фреймворк. Мы оставими Drupal для контент-менеджмента, но органичивая его от своей системы, позволили в будущем интегрироваться с новой CMS, которая, возможно, будет удобнее и быстрее. Таким образом пришли к пониманию Javascript, API, Markdown подхода.

Вывод

Нужно тщательно выбирать итоговый стек технологий. Это и записываем в нашу книгу архитектора.

Финальная история

Кейс

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

Почему это произошло?

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

Вывод

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

Разработка архитектуры проекта, корабли и JavaScript - 28

Итоги

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

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

Если доклад понравился, обратите внимание: 24-25 ноября в Москве состоится новая HolyJS, и там тоже будет много интересного. Уже известная информация о программе — на сайте, и билеты можно приобрести там же.

Автор: Евгений Трифонов

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js