Никаких сомнений, что за последнее время мир только укрепил свою зависимость от программного обеспечения. Приложения должны обладать высокой доступностью, качественно выполнять требуемые функции и иметь адекватную стоимость. Эти характеристики, в той или иной степени, определяет архитектура ПО.
В стандарте IEEE 1471 дается следующее определение: «Архитектура – это базовая организация системы, которая описывает связи между компонентами этой системы (и внешней средой), а также определяет принципы её проектирования и развития». Однако многие другие определения архитектуры признают не только структурные элементы, но и их композиции, а также интерфейсы и другие соединительные звенья.
Пока что у нас нет общепринятой классификации архитектурных парадигм, но все-таки мы легко можем выделить несколько базовых и широко распространенных архитектурных паттернов или стилей. Взглянем на некоторые из них.
«Каналы и фильтры» (Pipes and Filters)
Рисунок 1 – Pipes and Filters
Этот вид архитектуры подходит в том случае, если процесс работы приложения распадается на несколько шагов, которые могут выполнятся отдельными обработчиками. Основными компонентами являются «фильтр» (filter) и «канал» (pipe). Иногда дополнительно выделяют «источник данных» (data source) и «потребитель данных» (data sink).
Каждый поток обработки данных – это серия чередующихся фильтров и каналов, начинающаяся источником данных и заканчивающаяся их потребителем. Каналы обеспечивают передачу данных и синхронизацию. Фильтр же принимает на вход данные и обрабатывает их, трансформируя в некое иное представление, а затем передает дальше.
К примеру, один из фильтров может реализовывать шифр Цезаря – шифр подстановки, в котором каждый символ в тексте заменяется символом, находящимся на некотором постоянном числе позиций левее или правее в алфавите. Одна из вариаций кода Цезаря – это ROT13, имеющая шаг равный 13.
Рисунок 2 – Принцип ROT13
Его достаточно просто реализовать с помощью стандартной терминальной утилиты Unix tr:
$ # Map upper case A-Z to N-ZA-M and lower case a-z to n-za-m
$ echo "The Quick Brown Fox Jumps Over The Lazy Dog" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
Gur Dhvpx Oebja Sbk Whzcf Bire Gur Ynml Qbt
$ tr 'A-Za-z' 'N-ZA-Mn-za-m' <<<"The Quick Brown Fox Jumps Over The Lazy Dog"
Gur Dhvpx Oebja Sbk Whzcf Bire Gur Ynml Qbt
А вот код на Python:
def rot13(text):
rot13ed = ''
for letter in text:
byte = ord(letter)
capital = (byte & 32)
byte &= ~capital
if ord('A') <= byte <= ord('Z'):
byte -= ord('A')
byte += 13
byte %= 26
byte += ord('A')
byte |= capital
rot13ed += chr(byte)
return rot13ed
Фильтры можно легко заменять, использовать повторно, переставлять местами, что дает возможность реализовывать множество функций на основе ограниченного набора компонентов. Более того, активные фильтры могут работать параллельно, что приводит к значительному повышению производительности на многопроцессорных системах. Однако есть и недостатки, например, фильтры зачастую тратят больше времени на преобразование входных данных, чем на их обработку.
В качестве примера использования этой архитектуры может служить оболочка UNIX Shell, одним из дизайнеров которой был Дуглас Макилрой (Douglas McIlroy). Другим примером может стать архитектура компилятора, если рассматривать её как последовательность фильтров: лексера, парсера, семантического анализатора и генератора кода.
Дополнительную информацию по этому типу архитектуры можно найти здесь и здесь.
Многоуровневая архитектура
Является одной из самых известных архитектур, в которой каждый слой выполняет определенную функцию. В зависимости от ваших нужд вы можете реализовать любое количество уровней, но слишком большое их количество приведет к чрезмерному усложнению системы. Часто выделяют три основных уровня: уровень представления, уровень логики и уровень данных.
Рисунок 2 – Многоуровневая архитектура
Слою не обязательно знать, что делают его соседи. Здесь проявляется такое свойство как разделение ответственности. Если все три слоя являются закрытыми, то запрос пользователя к верхнему уровню инициирует цепочку обращений с верхнего уровня до самого нижнего. В этом случае уровень представления отвечает за пользовательский интерфейс и отображение данных для пользователя и ничего не знает о существовании физического хранилища данных. Ничего о существовании базы данных не знает и уровень логики – его «беспокоят» только правила бизнес-логики. Доступ к базе данных имеет лишь через уровень управления данными.
Достоинствами применения такой архитектуры являются простота разработки (в основном из-за того, что этот вид архитектуры всем знаком) и простота тестирования. Среди недостатков можно выделить возможные сложности с производительностью и масштабированием – всему виной необходимость прохождения запросов и данных по всем уровням (опять же, в том случае, если все слои являются закрытыми).
Одним из самых известных примеров этого паттерна может служить сетевая модель OSI. Подробнее о многоуровневой архитектуре можно почитать, например, в блогах вот этих трех программистов и разработчиков:
- Блог Скотта Хансельмана (Scott Hanselman),
- Блог Хендри Люка (Hendry Luk),
- Блог Йоханнеса Бродвалла (Johannes Brodwall).
Архитектура, управляемая событиями (EDA)
Это популярный адаптивный паттерн, широко используемый для создания масштабируемых систем. Чтобы ознакомиться с принципами событийно-ориентированной архитектуры, можете посмотреть вот это видео от Complexity Academy:
Рисунок 3 – Событийно-ориентированная архитектура
Если говорить о программном обеспечении, то в этой схеме существует два варианта событий: инициализирующее событие и событие, на которое реагируют обработчики. Обработчики являются изолированными независимыми компонентами, отвечающими (в идеале) за какую-нибудь одну задачу, и содержат бизнес-логику, необходимую для работы.
Посредник может быть реализован несколькими способами. Самый просто способ – это воспользоваться фреймворками для интеграции Apache Camel, Spring Integration или Mule ESB. Для больших приложений, которым требуется более сложные функции управления, вы можете реализовать посредника, используя концепцию управления бизнес-процессами (например движок jBPL).
Архитектура, управляемая событиями – это относительно сложный паттерн. Причиной тому – его распределенная и асинхронная природа. Вам придется решать проблемы фрагментации сети, обрабатывать ошибки очереди событий и так далее. Плюсами этой архитектуры могут служить высокая производительность, легкость развертки и поразительные возможности масштабирования. Однако возможно усложнение процесса тестирования системы.
Подробнее о событийно-ориентированной архитектуре можно почитать здесь.
Микроядерная архитектура
Паттерн состоит из двух компонентов: основной системы (ядра) и плагинов. Ядро содержит минимум бизнес-логики, но руководит загрузкой, выгрузкой и запуском необходимых плагинов. Таким образом, плагины оказываются несвязанными друг с другом.
Поскольку плагины могут разрабатываться независимо друг от друга, такие системы обладают очень высокой гибкостью и, как следствие, легко тестируются. Производительность приложения, построенного на основе такой архитектуры, напрямую зависит от количества подключенных и активных модулей.
Возможно самым лучшим примером микроядерной архитектуры будет Eclipse IDE. Скачивая Eclipse без надстроек, вы получаете совершенно пустой редактор. Однако с добавлением плагинов пустой редактор начнет превращаться в полезный и легко настраиваемый продукт. Еще один хороший пример – это браузер: дополнительные плагины позволяют расширить его функциональность.
Подробнее о микроядерной архитектуре можно узнать здесь и здесь.
Микросервисная архитектура
Этот тип архитектуры позволяет масштабировать приложения по оси Y «Куба масштабирования» (Scale Cube), описанного в книге «The Art of Scalability» Мартина Эбботта (Martin L. Abbott) и Майкла Фишера (Michael T. Fisher). В этом случае приложение разбивается на множество небольших сервисов, называемых микросервисами. Каждый микросервис включает в себя бизнес-логику и представляет собой совершенно независимый компонент. Сервисы одной системы могут быть написаны на различных языках программирования и общаться друг с другом, используя различные протоколы.
Поскольку каждый микросервис является отдельным проектом, вы можете распределить работу над ними между командами разработчиков, то есть над системой могут одновременно трудиться несколько десятков программистов. Микросервисная архитектура позволяет с легкостью масштабировать приложение – если вам понадобилось внедрить новую функцию (развертывать каждый микросервис можно по отдельности), просто напишите новый сервис, а если какой-то функцией никто не пользуется – отключите сервис.
Очевидным недостатком этого паттерна является необходимость передачи большого количества данных между микросервисами. Если накладные расходы на обмен сообщениями слишком велики, нужно либо оптимизировать протокол, либо объединить микросервисы. С тестированием таких систем тоже не все просто. Например, если вы пишите класс на Spring Boot, который должен протестировать все REST API сервиса, то он должен запустить проверяемый микросервис и микросервисы с ним связанные. Это не ядерная физика, но недооценивать сложность процесса все же не стоит.
Подробнее о микросервисных архитектурах вы можете почитать в следующих источниках: здесь, тут и вот тут.
Дополнительные материалы
В рамках этой статьи мы рассмотрели несколько видов архитектур программного обеспечения. Разумеется, здесь не было затронуто множество других паттернов. В качестве дополнительных материалов по теме можете посмотреть следующие ресурсы:
- O’Reilly: серия статей об архитектурах программного обеспечения;
- SE: лучшие книги по теме архитектуры программного обеспечения;
- Fromdev: пять лучших книг по архитектуре ПО, которые должен прочесть каждый;
- All Things Distributed: блог Вернера Вогелса (Werner Vogels), технического руководителя Amazon, о построении масштабируемых и надежных распределённых систем;
- Quora: лучшие книги, статьи и блоги для архитекторов ПО.
В нашем блоге рассматриваются самые разные вопросы от направлений развития файловых систем до отраслевых мероприятий, от темы надежности ЦОД до виртуализации и собственных кейсов по переводу российского бизнеса в «облако».
Помимо этого мы рассказываем о работе службы поддержки «облачного» провайдера и теме оптимизации производительности, к обсуждению которой мы планируем вернуться в одном из следующих материалов.
Автор: ИТ-ГРАД