Хабрахабр

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вывод

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

RACI

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

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

Onion

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

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

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

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

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

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

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

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

Что сделали

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

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

Вывод

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

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

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

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

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

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

Что сделали

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

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

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

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

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

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

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

Вывод

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

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

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

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

Что сделали

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

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

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

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

Вывод

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Кейс

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вывод

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

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

Кейс

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

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

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

Вывод

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

Итоги

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть