Архитектура приложений и интеграции: гайд по основным понятиям простыми словами

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

Что такое архитектура и для чего она нужна

Архитекту́ра или зодчество, согласно википедии, это искусство и наука строить, проектировать здания и сооружения (включая их комплексы), а также сама совокупность зданий и сооружений, создающих пространственную среду для жизни и деятельности человека.

В той же википедии есть определения того, что такое программная архитектура

Архитектура программного обеспечения (англ. software architecture) совокупность важнейших решений об организации программной системы, включающая в себя:

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

Наиболее понятным, на мой взгляд, кажется определение, которое объединит эти два понятия:

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

Проще говоря, если мы решили использовать HEAD-FIRST подход (сначала думай, потом делай), то без проработки архитектуры нам не обойтись. Да и в ситуации, когда мы сначала всё сделали, а потом начали думать – к вопросу архитектуры мы тоже придем, только теперь с большим объемом кода, который надо переписывать.

Виды архитектуры ПО

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

Во-первых, микросервисная архитектура является подтипом сервис-ориентированной архитектуры.

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

Итак, монолит – это иерархическая архитектура, т.е каждый слой приложения отвечает за свою часть функционала, например: работа с базой, логирование, интерфейс (простота E2E тестирования, простота развертки). Глубоко разбирать монолиты не будем. Отметим, что есть несколько видов наиболее популярных шаблонов монолита:

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

  • Модель (Model) предоставляет данные и реагирует на команды контроллера, изменяя своё состояние.
  • Представление (View) отвечает за отображение данных модели пользователю, реагируя на изменения модели.
  • Контроллер (Controller) интерпретирует действия пользователя, оповещая модель о необходимости изменений.

Model-View-Presenter (MVP) — шаблон проектирования, производный от MVC, который используется в основном для построения пользовательского интерфейса.
Элемент Presenter в данном шаблоне берёт на себя функциональность посредника (аналогично контроллеру в MVC) и отвечает за управление событиями пользовательского интерфейса (например, использование мыши) так же, как в других шаблонах обычно отвечает представление.

Model-View-ViewModel (MVVM) — шаблон проектирования архитектуры приложения, представленный в 2005 году Джоном Госсманом (John Gossman) как модификация шаблона Presentation Model. Ориентирован на современные платформы разработки, такие как Windows Presentation Foundation, Silverlight от компании Microsoft, ZK framework.

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

Про масштабируемость:
  • Монолит - масштабируется не рационально(поднимаем всё) + не всегда возможно, если монолит изначально не писался с учетом масштабируемости
  • Микросервисы масштабируются рационально (увеличиваем количество экземпляров только нужных сервисов.)

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

Лайфхак: никто не догадается о том, что монолит, это монолит, если не допускать людей к кодовой базе! :)

Виды интеграций

Первое, и самое важное, что нужно понять, прежде чем мы поговорим про интеграции - чтобы произвести интеграцию, нужно больше одного приложения.
Что это означает: если у Вас есть монолитное приложение (один .exe файл), и в этом приложении один метод вызывает другой через, например, REST API, то это просто ненужное усложнение.

Интеграция, как следует из перевода, это «внедрение». Соответственно, внедрение одного приложения в другое – целесообразно, а внедрение методов приложения один в другой – совершенно бессмысленно
.
Представим, что у нас есть 2 приложения (для наглядности, пусть это будут 2 разных приложения на 2-х разных машинах), которые мы хотим между собой подружить. Начнем!

Первый вопрос, которым мы зададим себе - какой вид обмена нам нужен: синхронный, либо асинхронный?

Разберём, что такое синхронное взаимодействие.

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

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

  • 100 – 199 (информационные) – уточняющие вопросы от кассира, по поводу того, какой именно хлеб нужен, либо кассир пошел за хлебом на склад.
  • 200 – 299 (успешные) – кассир отдает Вам хлеб.
  • 300 – 399 (перенаправление) – у кассира хлеба нет, но он есть в соседнем магазине, о чем он Вам и сообщает
  • 400-499 (клиентские) – пожалуй, самые часто встречающиеся ошибки, например, при интернет-серфинге. Означает, что запрос составлен некорректно, или цитируя группу “Научно-технический рэп” - «не по понятиям». Означает, что, например, у кассира хлеб есть, но конкретно фиолетовый хлеб со вкусом утренней росы, который вы запросили - в ассортименте магазина отсутствует.
  • 500-599 (ошибки сервера) – что-то не то с самим кассиром, например он уснул или идентифицирует себя не как кассир, а как булка хлеба, в связи с чем не хочет продавать вам своего собрата.

В данном примере мы получим какой-то результат взаимодействия с магазином и только после этого пойдем заниматься другими делами.

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

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

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

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

Файловый обмен

Данный метод интеграции появился достаточно давно и проверен временем. Смысл метода в том, что система номер 1 передает в систему номер 2 файл в установленном формате(например csv).

Плюсы данного подхода:

  • Простота
  • Отсутствие необходимости соединения между системами

Недостатки:

  • Скорость
  • Ненадежность
  • Отсутствие возможности получить информацию о валидности файла со стороны вызывающей системы

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

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

Данный метод интеграции предполагает, что 2 приложения используют общую базу данных.
На самом деле для реализации данного подхода не обязательно, чтобы база была общей. Например в СУБД Oracle присутствует механизм database links, который позволяет получать в одной базе данных данные из другой.

Плюс данного подхода:

  • Простота
Недостаток:

  • Подход создает сильную связанность между системами

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

Удалённый вызов

Идея данного метод интеграции в том, что система 1 удаленно вызывает метод системы 2, передавая туда нужные параметры. Способов, как конкретно можно реализовать данную идею довольно много. К методам удаленного вызова можно отнести, например, интеграцию через http/https, с использованием REST, SOAP, GRPC и т.д.

Поскольку такой вид интеграции сейчас достаточно популярен, рассмотрим его реализации более подробно:

  1. GRPC – opensource фреймворк для удаленного вызова процедур.
Все запросы в рамках GRPC строго типизированные, для их описания используются proto-файлы.

Также к дополнительным плюсам GRPC относятся возможность отмены выполняемого запроса со стороны клиента и возможность стриминга, т.е передачи данных потоком.

Стоит отдельно выделить, что прото-файлы также поддерживают комментирование, что очень удобно.

В целом proto-файлы оформляются в стиле, похожем на код, а если быть точным, то они описываются языком Interface Definition Language (IDL), что делает возможным генерировать сервисы на основе прото-файлов.

Пример прото-файла:

// The greeter service definition.
	service Greeter {
 		 // Sends a greeting
 		 rpc SayHello (HelloRequest) returns (HelloReply) {}
	}
 
	// The request message containing the user's name.
	message HelloRequest {
  		string name = 1;
	}
 
	// The response message containing the greetings
	message HelloReply {
  		string message = 1;
	}
Как видим файл достаточно нагляден.

RPC позволяет реализовать как синхронное, так и асинхронное взаимодействие между сервисами.

На картинке мы можем увидеть стандартную схему применения GRPC:

У нас есть сервер GRPC, написанный на каком-либо языке (в данном случае C++) и два клиента, написанных либо на том же, либо на других языках. Соответственно, сервер может общаться одновременно с большим количеством клиентов.
GRPC используется поверх протокола HTTP/2.

GRPC также поддерживает передачу заголовков (метаданных). Что интересно – при отправке данных через GRPC поддерживается сжатие данных, т.е весь запрос, включая заголовки, сжимается, а при получении данных распаковывается. Т.е при отправке данные преобразуются в двоичный формат, а затем при получении распаковываются обратно. Такой подход увеличивает скорость передачи данных GRPC , по сравнению, например, с REST.

  1. SOAP - simple Object Access Protocol («простой протокол доступа к объектам»).
SOAP появился достаточно давно, в 1998 и был одним из первых протоколов, предназначенных для работы в парадигме RPC. Поскольку это был один из первых протоколов такого назначения, он имеет ряд недостатков:

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

Структура SOAP запроса:

Envelope («конверт»). Это корневой элемент. Определяет XML-документ как сообщение SOAP с помощью пространства имен xmlns:soap=»http://www.w3.org/2003/05/soap-envelope/». Если в определении будет указан другой адрес, сервер вернет ошибку.

Header («заголовок»). Включает в себя атрибуты сообщения, связанные с конкретным приложением (аутентификация, проведение платежей и так далее). В заголовке могут использоваться три атрибута, которые указывают, как принимающая сторона должна обрабатывать сообщение, — mustUnderstand, actor и encodingStyle. Значение mustUnderstand — 1 или 0 — говорит принимающему приложению о том, следует ли распознавать заголовок в обязательном или опциональном порядке. Атрибут actor задает конкретную конечную точку для сообщения. Атрибут encodingStyle устанавливает специфическую кодировку для элемента. По умолчанию SOAP-сообщение не имеет определенной кодировки.

Body («тело»). Сообщение, которое передает веб-приложение. Может содержать запрос к серверу или ответ от него.

Пример сообщения, которое запрашивает стоимость ноутбука в онлайн-магазине:

<?xml version="1.0"?>
<soap:Envelope
	xmlns:soap="http://www.w3.org/2003/05/soap-envelope/"
	soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Body> 
	<m:GetPrice xmlns:m="https://online-shop.ru/prices">
		<m:Item>Dell Vostro 3515-5371</m:Item>
	</m:GetPrice>
</soap:Body>
</soap:Envelope>
Пример ответа сервера онлайн-магазина:

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/"
	soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Body>
	<m:GetPriceResponse xmlns:m="https://online-shop.ru/prices">
		<m:Price>37299</m:Price>
	</m:GetPriceResponse>
</soap:Body>
</soap:Envelope>
Fault («ошибка»). Опциональный элемент. Передает уведомление об ошибках, если они возникли в ходе обработки сообщения. Может содержать вложенные элементы, которые проясняют причину возникновения ошибки:

  • faultcode — код неполадки;
  • faultstring — «человекопонятное» описание проблемы;
  • faultactor — информация о программном компоненте, который вызвал ошибку;
  • detail — дополнительные сведения о месте возникновения неполадки.
SOAP может использоваться поверх протоколов SMTP, FTP, HTTP, HTTPS.

  1. REST - архитектурный стиль взаимодействия компонентов распределённого приложения в сети.

Другими словами, REST — это набор правил того, как программисту организовать написание кода серверного приложения, чтобы все системы легко обменивались данными и приложение можно было масштабировать. REST представляет собой согласованный набор ограничений, учитываемых при проектировании распределённой гипермедиа-системы. В определённых случаях (интернет-магазины, поисковые системы, прочие системы, основанные на данных) это приводит к повышению производительности и упрощению архитектуры. В широком смысле компоненты в REST взаимодействуют наподобие взаимодействия клиентов и серверов во Всемирной паутине. REST является альтернативой RPC.

В случае, если приложение построено, в соответствии с парадигмой REST (не противоречит ей), то такое приложение называют RESTful.

Но как же так вышло, что в данном определении указано, что REST это альтернатива RPC, а мы его отнесли к RPC способам интеграции?

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

Концепция REST не предполагает, что у объекта на сервере есть состояние + в концепции REST все объекты имеют глаголы:

  • GET — получение ресурса
  • POST — создание ресурса
  • PUT — обновление ресурса
  • DELETE — удаление ресурса

Т.е, если нам нужно, например, изменить состояние объекта на сервере, то в случае с RPC это будет выглядеть, как
“POST /modifyItem” и далее в теле запроса мы передаем информацию об объекте, который мы хотим изменить. Т.е мы вызываем метод класса modifyItem

В случае с REST запрос будет выглядеть так:
“PUT /items/456“ и в теле запроса передается информация о том, к какому состоянию должен быть приведен объект с идентификатором 456.

Простыми словами:

  • REST = глагол + тип объекта + идентификатор + параметры запроса
  • RPC = метод + параметры метода
  • REST запрос обычно состоит из тела, заголовков и параметров запроса.

Также для REST API существует такое понятие как «степень зрелости REST API». Данная модель позволяет оценить, насколько веб приложение RESTful.
Уровень 0: Собачье болото (The Swamp of POX)

В википедии данный уровень зрелости называется болотом оспы, но мне такое название не очень нравится(звучит противно), поэтому добавим немного поэзии и переведем The Swamp of Plain Old XML, как болото Простого Старого Ыксемеля, что сокращенное читается, как ПСЫ, а значит собачье.

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

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


Уровень 1: Уровень Ресурсов

Данный уровень предполагает, что для каждого ресурса используется свой URL. Т.е если у нас есть API, то для каждого действия над каждым ресурсом будет использоваться свой URL, например:
http://api.articles.com/article/addNewArticle
http://api.articles.com/article/dropArticle
и т.д.

Уровень 2: Уровень методов

Уровень предполагает создание CRUD операции в ресурсах, почему бы не найти способ применить эти операции для всех ресурсов.
Можно реализовать данную идею, использую HTTP глаголы. Если мы хотим получить список страниц – мы можем сделать Get запрос к ресурсу /page, но если мы хотим создать новую страницу, мы можем использовать POST глаголы, вместо GET к тому же ресурсу - /page.

Уровень 2.1: HTTP заголовки

HTTP заголовки, это одно из базовых правил разработки REST API. Они нужны для того, чтобы передать больше информации о самом ресурсе: метаданные, данные авторизации и другое.
Заголовки также предоставляют запрошенную информацию о запросе и ответе, либо об объекте, отправленном в теле сообщения.

Существует 4 вида заголовков:

  • Общие заголовки: эти поля заголовков несут общую информацию, которая может быть использована как для запросов, так и для ответов.
  • Заголовки запроса клиента: эти поля применяются для запросов и ответов.
  • Заголовки ответа сервера: эти заголовки применяются только для сообщений ответа.
  • Заголовки сущностей: эти заголовки определяют мета информацию о сущности тела запроса или, если у запроса нет тела, то о ресурсах, определенных в запросе
Уровень 2.2: Параметры запроса

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

  • Пагинация – необходима для разбивки данных на части определенного объема.
  • Фильтрация – фильтрация ограничивает число возвращаемых значений, за счет ограничений по определенным атрибутам и их предполагаемым значениям. Существует возможность фильтровать коллекцию по нескольким атрибутам одновременно, для того, чтобы допустить возможность вернуть несколько значений для одного фильтруемого атрибута.
  • Сортировка – сортировка результата запроса к коллекции ресурсов. Параметры сортировки должны содержать имена атрибутов, к которым она применяется, и разделяться запятой.
  • Поиск – поиск, это под ресурс-коллекции. Его ответ имеет отличный формат, чем коллекция сама по себе. Это позволяет нам добавить предположения, корректировки и информацию, связанную с поиском.

Уровень 2.3: Коды статусов

Очень важно, при проектировании REST API использовать соответствующие HTTP-коды статусов, особенно при разработке и тестировании RESTful API.

Наиболее часто используемые коды статусов:

  • 200 - ОК: Всё работает
  • 201 – CREATED: Был создан новый ресурс
  • 204 – NO CONTENT: Ресурс был успешно удален, нет тела ответа
  • 304 – NOT MODIFIED: Возвращенные данные, это кэшированные данные (данные не изменились)
  • 400 – BAD REQUEST: Запрос некорректный или не может быть обработан. Конкретная ошибка должна быть объяснена в теле ответа, например «JSON IS Invalid»
  • 401 – UNATHORIZED: Запрос требует аутентификации пользователя.
  • 403 – FORBIDDEN: Сервер принял запрос, но отклонил, либо доступ к ресурсу запрещен
  • 404 - NOT FOUND: Нет ресурса, расположенного по данному URL
  • 500 - INTERNAL SERVER ERROR При разработке API стоит избегать данной ошибки. Все внутренние ошибки должны обрабатываться сервером и возвращаться в читаемом виде.

Уровень 3: Hypermedia Controls

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

Он состоит из двух частей:

  • Определение контента – отвечает за определение вида контента
  • HATEOS – отвечает за действия, производимые над ресурсом

Определение контента

Обычно определение контента производится соответствующим заголовком. Например Content-type.

HATEOS

Расшифровывается как Hypermedia As Transfer Engine Of Application State (Hypermedia как механизм передачи состояния приложения). Это ограничение REST приложений, которое отличает их от приложений другой архитектуры.

При таком подходе клиенту предоставляется описание ресурсов и их доступных действий. Т.е описание взаимодействие с сервером приходит в метаданных.

Пример HATEOS ответа:

{ 
    "name": "John Doe", 
    "self": "http://localhost:8080/user/123", 
    "posts": "http://localhost:8080/user/123", 
    "address": "http://localhost:8080/user/123/address" 
}
Уровень 3.1 Версионирование

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

Существует множество способов версионирования API, но приведем два наиболее используемых:

  • Заголовки.
  • Это могут быть настраиваемые заголовки, вроде X-API-VERSION, или любой другой
  • Либо заголовок «Accept», например «Accept: application/vnd.hashmapinc.v2+json»
  • URL.
  • Тут все просто. Версия указывается в запросе, например POST /v2/user.

Способы организации Микросервисной Архитектуры
Существует 2 способа организации МСА: оркестрация и хореография

  • Оркестрация сервисов

Оркестрация служб представляет собой единый централизованный исполняемый бизнес-процесс (оркестратор), который координирует взаимодействие между различными службами (например Camunda). Оркестратор отвечает за вызов и объединение служб.

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

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