Фундаментальные архитектурные паттерны ПО

Фундаментальные архитектурные паттерны ПО
Фото Josephine Baran на Unsplash
👋
Хочешь поучаствовать в жизни сайта? Мы ищем авторов!
Перевод статьи Williams O: Fundamental Software Architectural Patterns.
Оглавление

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

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

Чем архитектурные паттерны отличаются от паттернов проектирования?

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

Слоеный паттерн

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

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

Пример слоеное паттерна

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

Клиент-серверный паттерн

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

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

Пример "Клиента-Сервера"

Паттерн "Пайпы и фильтры"

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

Этот паттерн широко используется в анализе и преобразовании данных. Более знакомый всем пример этого паттерна - использование unix pipe (|) для сцепления команд друг с другом.

Диаграмма с примером такого паттерна:

Пример пайпов и фильтров

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

  • Фильтр - это компонент, который читает данные, преобразует их и возвращает преобразованные данные.
  • Пайп (pipe) - связующее звено, которое передает данные от одного фильтра к другому, не допуская их изменение по пути.

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

Паттерн SOA

В сервисно-ориентированной архитектуре (Service-Oriented-Architecture, SOA) независимые компоненты реализуются в виде сервисов, которые предлагают некоторый функционал. Эти сервисы объединяются во время выполнения для определения поведения системы в целом. Чтобы это сработало, потребители этих сервисов должны иметь возможность найти нужные сервисы и использовать, не зная всех деталей реализации, лежащих в их основе.

Этот тип архитектуры может быть реализован различными способами. Традиционные системы SOA в значительной степени полагаются на протокол SOAP, который работает путем обмена XML-сообщениями, в то время как более "современные" приложения SOA поощряют использование микросервисов, которые связываются легковесными сообщениями по протоколу HTTP или наподобие.

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

Обобщенный пример SOA

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

Паттерн Pub-Sub

В паттерне Publish-Subscribe (Публикация-Подписка, Pub-Sub) производители и потребители данных существуют независимо друг от друга и не знают друг друга. Суть паттерна в том, что несколько потребителей подписываются на события, публикуемые несколькими производителями. Обе стороны связываются косвенно через шину событий, которая отвечает за связь между производителями и заинтересованными подписчиками.

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

Пример Pub-Sub

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

Паттерн "Общие данные"

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

Пример паттерна "Общие данные"

Хотя у этой схемы и есть собственное название, обычно она рассматривается как часть другой более крупной системы, например, когда в архитектуре SOA различные сервисы обращаются к общей базе данных. Мы можем определить уровни доступа для чтения и записи с различными правилами и разрешениями, которые оптимизируют и защищают доступ к данным. Сегодня сложность такой архитектуры облегчается такими облачными сервисами, как AWS RDS, который обеспечивает работу баз данных с репликами для чтения, масштабируемостью и резервным копированием, облегчая их управление и настройку.

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

Паттерн P2P

Одноранговые (peer-to-peer, p2p) архитектурные паттерны относятся к категории симметричных клиент-серверных паттернов. "Симметричный" в данном контексте означает, что в таких сетях нет строгого разделения на клиентов, серверов и прочее. В одноранговой архитектуре одна система выступает в качестве и клиента, и сервера.

Каждая система, называемая также пиром (peer), посылает запросы другим пирам по сети и одновременно получает и обрабатывает запросы от других пиров, состоящих в этой сети. Эта архитектура сильно отличается от традиционной клиент-серверной, в которой клиент может только отправлять запросы и ждать, пока сервер их обработает.

Пример P2P-сети

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

Паттерн "Сервисный брокер"

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

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

Серверы уведомляют о своих возможностях (сервисах и характеристиках) брокера. Клиенты запрашивают сервис у брокера, а брокер перенаправляет клиента на подходящий сервис из своего реестра. Хорошими примерами ПО брокеров сообщений являются Apache ActiveMQ, Apache Kafka, RabbitMQ.

Вот пример того, как такая система могла бы выглядеть на диаграмме:

Пример системы с брокером

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

Найдите свою архитектуру

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

Бонус: Несоответствие архитектуры

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

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

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


Вот в общем-то и все. Спасибо за чтение!

Подпишитесь за меня в твиттере, чтобы не потерять новые посты и другие крутые штуки.

Пока! 🤘

Материал подготовлен с ❤️ редакцией Кухни IT.

Олег Ямников

Олег Ямников

Главный кухонный корреспондент.