Марк Ричардс, архитектор ПО из Бостона, уже более 30 лет размышляет о том, как должно работать ПО. В его бесплатной книге «Паттерны архитектуры программного обеспечения» (Software Architecture Patterns) описаны архитектуры, которые часто встречаются в программных системах. Портал TechBeacon представляет пять основополагающих архитектур Ричардса в виде краткого справочника сильных и слабых сторон, а также оптимальных вариантов использования.

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

Помните, что хотя это и называется компьютерной наукой, зачастую это искусство.

Многоуровневая (n-уровневая) архитектура

Это естественная модель для разбиения сложных проблем на более мелкие, более управляемые части, которые можно делегировать отдельным командам. Некоторые считают, что это самая распространенная архитектура, хотя это утверждение является чем-то вроде самосбывающегося пророчества. Многие из самых больших и лучших программных фреймворков — React, Java EE, Drupal и Express — были созданы с учетом этой структуры, поэтому многие приложения, созданные с их помощью, естественно, имеют многоуровневую архитектуру.

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

Структура Model-View-Controller («Модель-Представление-Контроллер») является стандартным подходом к разработке ПО, предлагаемым большинством популярных веб-фреймворков. Это явно многоуровневая архитектура. Сразу над базой данных находится слой модели, который часто содержит бизнес-логику и информацию о типах данных в базе данных. Сверху находится слой представления, который часто представляет собой CSS, JavaScript и HTML с динамическим встроенным кодом. В середине находится контроллер, который содержит различные правила и методы для преобразования данных, перемещающихся между представлением и моделью.

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

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

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

Проблемы этого подхода:

  • исходный код может превратиться в «большой ком грязи», если он неорганизован, а модули не имеют четких ролей или взаимосвязей;
  • код может получиться медленным из-за того, что некоторые разработчики называют «антипаттерном воронки» (sinkhole anti-pattern). Большая часть кода может быть посвящена передаче данных через слои без использования какой-либо логики;
  • изоляция слоев, являющаяся важной целью для этой архитектуры, также может затруднить понимание архитектуры без понимания каждого модуля;
  • кодеры могут пропускать слои для создания тесной связи и создавать логический беспорядок, полный сложных взаимозависимостей. Тогда она может стать похожей на подход микроядра, описанный ниже;
  • монолитное развертывание часто неизбежно, что означает, что небольшие изменения могут потребовать полного повторного развертывания приложения.

Эта архитектура лучше всего подходит для:

  • новых приложений, которые необходимо быстро создать;
  • корпоративных или бизнес-приложений, которые должны отражать деятельность традиционных ИТ-отделов и их процессы;
  • команд с неопытными разработчиками, которые еще не понимают других архитектур;
  • приложений, требующих строгих стандартов сопровождаемости и тестируемости;
  • конвейеров данных, созданных для науки о данных на таких языках, как R и Python.

Архитектура, управляемая событиями

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

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

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

В целом, архитектуры, управляемые событиями:

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

Проблемы, связанные с этим подходом:

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

Событийно-ориентированная архитектура лучше всего подходит для:

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

Микроядро, или архитектура плагинов

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

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

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

Ричардс объясняет это на примере страхового бизнеса: «Обработка претензий непременно сложна, но фактические шаги не являются сложными. Сложной ее делают все правила». Решение заключается в том, чтобы перенести некоторые базовые задачи — например, запрос имени или проверку оплаты — в микроядро. Они могут быть протестированы независимо, а затем различные бизнес-подразделения могут написать плагины для различных типов претензий, связывая правила с вызовами основных функций в ядре.

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

Проблемы, связанные с этим подходом:

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

Микроядро лучше всего подходит для:

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

Архитектура микросервисов

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

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

«Если вы перейдете на iPad и посмотрите на пользовательский интерфейс Netflix, каждая вещь в этом интерфейсе поступает из отдельного сервиса, — говорит Ричардс. — Боковые панели и меню, заполненные рейтингами просмотренных фильмов, рекомендации, список „Что будет дальше“ и данные счета отслеживаются отдельными сервисами и предоставляются независимо друг от друга». Как будто Netflix — или любой другой микросервис — представляет собой созвездие десятков небольших сайтов, которые представляют себя как единый сервис.

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

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

Проблемы, связанные с микросервисным подходом:

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

Этот подход лучше всего подходит для:

  • веб-сайтов с небольшими компонентами;
  • веб-приложений, построенных на основе серверного JavaScript для Node-приложений, таких как React или Vue;
  • корпоративных дата-центров с четко определенными границами;
  • быстро развивающихся новых предприятий и веб-приложений;
  • команд разработчиков, которые разбросаны, часто по всему миру.

Пространственная архитектура

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

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

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

«Это все объекты в памяти, — поясняет Ричардс. — Архитектура на основе пространства поддерживает непредсказуемые скачки, исключая базу данных».

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

Проблемы при использовании пространственного подхода:

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

Эта архитектура лучше всего подходит для:

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

Подбирайте и смешивайте

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