виды архитектуры веб приложений
Архитектура и архитектурные стили веб-приложений – 1
Предлагается справочная информация по основным архитектурным стилям веб-приложений с основными терминами по мотивам диссертации Роя Т. Филдинга (автора REST). В первой части предлагается список архитектурных стилей и таблица их сравнения. Во второй планируется описание самих стилей.
Введение
Зачем это нужно?
Для того, чтобы успешно проектировать приложения, взаимодействующие через сеть, необходимо знать основные архитектурные стили, которые могут дать вашей архитектуре требуемые свойства.
О каких веб-приложениях идет речь?
Network-based hypermedia systems.
Сравнение стилей
Определения
Архитектура (программной системы) – это модель элементов программной системы во время определенной фазы ее работы.
Программная система может рассматриваться на различных уровнях детализации, также может рассматриваться множество фаз ее работы (запуск, инициализация, процесс выполнения, реинициализация, завершение). Каждому уровню детализации и каждой фазе работы соответствует своя архитектура.
Архитектурный стиль – упорядоченное множество архитектурных ограничений, которые определяют роли и характеристики архитектурных элементов и разрешенные виды отношений между этими элементами для любой архитектуры, которая соответствует этому стилю.
Таблица Архитектурные стили (Styles) / Архитектурные свойства (Properties)
Первый столбец – названия стилей, второй – названия родительских стилей (для тех, у которых он есть).
Значение в таблице показывает относительное влияние архитектурного стиля на соответствующее архитектурное свойство. Минус (–) означает негативное влияние, плюс (+) положительное, а (±) означает что влияние зависит от дополнительных условий.
Список рассматриваемых стилей и свойств идет после таблицы.
Стили (styles)
Свойства (properties)
Архитектура веб приложения: компоненты, слои и типы
Что такое архитектура веб-приложений? Из этого руководства вы можете изучить основы архитектуры веб-приложений. Мы обсудим, как работает архитектура веб-приложения, какие компоненты, слои и модели существуют
Логика довольно проста: когда пользователь вводит URL-адрес в браузере и нажимает «ввод», браузер делает запрос к серверу. Сервер отвечает, а затем показывает требуемую веб-страницу. Все эти компоненты создают архитектуру веб-приложения.
При правильной работе клиентская и серверная стороны составляют архитектуру программного обеспечения веб-приложения.
Слои и компоненты архитектуры веб-приложений
Чтобы лучше понять архитектуру веб-приложения, вам следует погрузиться в его компоненты и уровни. Веб-приложения разделяют свои основные функции на уровни. Это позволяет заменять или обновлять каждый слой независимо.
Базовые компоненты архитектуры веб-приложений
Веб-архитектура имеет компоненты пользовательского интерфейса и структурные компоненты. Последние также делятся на клиентские и серверные.
Компоненты пользовательского интерфейса
Компоненты пользовательского интерфейса обозначают все элементы интерфейса, такие как журналы активности, информационные панели, уведомления, настройки и многое другое. Они являются частью макета интерфейса веб-приложения.
Структурные компоненты состоят из клиентской и серверной сторон:
Клиентский компонент разработан с HTML, CSS или JavaScript. Веб-браузеры запускают код и преобразуют его в интерфейс, поэтому нет необходимости в настройке операционной системы.
Уровни архитектуры веб-приложений
Существует четыре общих уровня веб-приложений:
Уровень службы данных
DSL передает данные, обработанные уровнем бизнес-логики, на уровень представления. Этот уровень гарантирует безопасность данных, изолируя бизнес-логику со стороны клиента.
Типы архитектуры веб-приложений
Можно выделить несколько типов архитектуры веб-приложений, в зависимости от того, как логика приложения распределяется между клиентской и серверной сторонами. Наиболее распространенные архитектуры веб-приложений:
Известные СПА : Gmail, Facebook, Twitter, Slack.
Многостраничное приложение или MPA
Многостраничные приложения более популярны в Интернете, так как в прошлом все веб-сайты были MPA. В наши дни компании выбирают MPA, если их веб-сайт довольно большой (например, eBay). Такие решения перезагружают веб-страницу для загрузки или отправки информации с / на сервер через браузеры пользователей.
Известные MPA: eBay, Amazon.
Что касается микросервисов, этот подход позволяет разработчикам создавать веб-приложение из набора небольших сервисов. Разработчики создают и развертывают каждый компонент отдельно.
Архитектура микросервисов выгодна для больших и сложных проектов, поскольку каждый сервис может быть изменен без ущерба для других блоков. Поэтому, если вам нужно обновить логику оплаты, вам не придется на время останавливать работу сайта.
Известные проекты: Netflix, Uber, Spotify, PayPal.
Что это означает?
Чтобы сохранить веб-приложение в Интернете, разработчики должны управлять серверной инфраструктурой (виртуальной или физической), операционной системой и другими процессами хостинга, связанными с сервером. Поставщики облачных услуг, такие как Amazon или Microsoft, предлагают виртуальные серверы, которые динамически управляют распределением машинных ресурсов. Другими словами, если ваше приложение испытывает огромный всплеск трафика, к которому ваши серверы не готовы, приложение не будет отключено.
Известные PWA: Uber, Starbucks, Pinterest.
Как разработать архитектуру для веб-приложения
Качественная архитектура веб-приложения делает процесс разработки более эффективным и простым. Веб-приложение с продуманной архитектурой легче масштабировать, изменять, тестировать и отлаживать.
1. Архитектура Web приложений¶
Web-приложение – клиент-серверное приложение, в котором клиент взаимодействует с веб-сервером при помощи браузера.
1.1. Разделение между Internet и WWW¶
1.1.1. Что такое Internet?¶
Инфраструктура, которая позволяет передавать любые типы данных между сетями и устройствами, подключенными к ней.
Частичная карта Интернета
Эта инфраструктура поддерживается сетевыми протоколами – алгоритмами, которые реализуют различные сервисы по передачи данных.
Существует несколько стеков сетевых протоколов, основные: OSI и ICP/IP. Рассмотрим на примере ICP/IP.
Стэк TCP/IP (представлены не все протоколы)
Из этого множества протоколов и уровней, в Web’e нас больше всего интересуют протоколы прикладного уровня (приложений). В частности: HTTP, FTP, DNS, SSH, P2P. Но для полноты понимания немного затронем и другие уровни.
1.1.2. Что такое WWW?¶
World Wide Web – распределённая система взаимосвязанных, посредством гиперссылок, документов и их ресурсов, располагающихся на машинах подключенных к Internet. Эта система посредством протоколов серверного и клиентского ПО обеспечивает доступ к этим вазимосвязанным документам.
Распределённая – означает, что физически, документы и их ресурсы могут располагаться на разных серверах.
Связанность документов и ресурсов посредством гиперссылок
Историческая справка – изначально, WWW разрабатывалась как система ссылок в научном сообществе.
1.1.3. Различия¶
Internet – это инфраструктура.
WWW – это сервис.
1.2. Клиент-серверная архитектура¶
Клиент-серверная архитектура – означет, что приложение исполняется одновременно на двух машинах: на клиенте и на сервере.
Web-сервера работают (как правило) на серверах в датацентрах. Их задача заключается в хранении (или генерации) и отдачи документов, то есть отвечать на запросы.
Схема клиент-серверной модели
1.3. Типы Web-документов (MIME-типы)¶
MIME-типы – типы данных, которые могут быть переданы, посредством сети Интернет, в Web. Нужны для того, чтобы браузер знал как обрабатывать тот или иной документ.
Расширения файлов играют второстепенную роль.
Документы могут быть:
1.3.1. Сокращенный перечень MIME-типов¶
URL (Uniform Resource Locator) – определитель местонахождения ресурса, документа. Другими словами – адрес.
Структура у него следующая:
Протокол – с помощью какого протокола загружать.
Параметры – список определённых значений, которые передаются вместе с запросом.
Якорь – определённое место в самом документе (Web-страничке).
1.4.1. Абсолютные и относительные URL¶
Абсолютный – полный и точный адрес.
Относительный – адрес, который высчитывается браузером в зависимости от текущего адреса.
1.4.2. Правила разрешения URL¶
Рассмотрим на примере – жирным выделено, какая часть добавляется браузером при обработке относительных адресов:
Чистая Архитектура для веб-приложений
Хочу поделиться с вами подходом который я уже много лет использую в разработке приложений, в том числе и веб-приложений. Многим разработчикам настольных, серверных и мобильных приложений этот подход хорошо знаком, т.к. является фундаментальным при построении таких приложений, однако в вебе он представлен очень скудно, хотя желающие использовать такой подход однозначно есть. Кроме того на таком подходе написан редактор VS Code.
В результате применения этого подхода вы отвяжетесь от конкретного фреймворка. Сможете легко переключать библиотеку представления внутри вашего приложения, например React, Preact, Vue, Mithril без переписывания бизнес логики, а в большинстве случаев даже вьюхи. Если у вас есть приложение на Angular 1, вы без проблем сможете перевести его на Angular 2+, React, Svelte, WebComponents или даже свою библиотеку представления. Если у вас есть приложение на Angular 2+, но нету специалистов для него, то вы без проблем сможете перевести приложение на более популярную библиотеку без переписывания бизнес логики. А в итоге вообще забыть про проблему миграции с фремворка на фреймворк. Что же это за магия такая?
Что такое Чистая Архитектура
Для того что бы понять это, лучше всего прочитать книгу Мартина Роберта «Чистая Архитектура» (Robert C.Martin «Clean Architecture»). Краткая выдержка из которого приведена в статье по ссылке.
Основные идеи заложенные в архитектуру:
Достигается такая гибкость за счет разделения приложения на слои Service, Repository, Model. Я же добавил к Чистой Архитектуре подход MVC и получил следующие слои:
Кому подойдет Чистая Архитектура
Веб разработка прошла большой путь развития, начиная от простого скриптования на jquery до разработки больших SPA приложений. И сейчас веб приложения стали настолько большими что количество бизнес логики стало сопоставимо или даже превосходить серверные, настольные и мобильные приложения.
Разработчикам которые пишут сложные и большие приложения, а также переносят бизнес логику с сервера на веб приложения для экономии на стоимости серверов, Чистая Архитектура поможет организовать код и отмасштабироваться без проблем до огромных масштабов.
В тоже время если ваша задача просто верстка и анимация лендингов, то Чистую Архитектуру просто некуда вставить. Если ваша бизнес логика находиться на бекенде и ваша задача получить данные, отобразить клиенту и обработать клик по кнопке, то вы не прочувствуете гибкости Чистой Архитектуры, но она может стать отличным плацдармом для взрывного роста приложения.
Где уже применяется?
Чистая архитектура не привязана к какому то конкретному фреймворку, платформе или языку программирования. Десятилетия ее используют для написания настольных приложений. Его эталонную реализацию можно найти во фреймворках для серверных приложений Asp.Net Core, Java Spring и NestJS. Так же она очень популярна при написании iOs и Android приложений. Но в веб разработке он предстал в крайне неудачном виде во фреймворках Angular.
Так как я сам не только Typescript, но и C# разработчик, то для примера возьму эталонную реализацию этой архитектуры для Asp.Net Core.
Вот упрощенный пример приложения:
Если вы не понимаете что в нем написано ничего страшного, дальше мы разберем его по частям каждый фрагмент.
Пример приведен для Asp.Net Core приложения, но для Java Spring, WinForms, Android, React архитектура и код будут такие же, меняется только язык и работа с вьюхой (если она есть).
Применение в веб-приложениях
Единственный фреймворк который пытался использовать Чистую архитектуру был Angular. Но получилось это просто ужасно, что в 1, что в 2+.
И причин для этого много:
Начинаем создавать приложение
Рассматривать Чистую Архитектуру будем на примере выдуманного приложения, максимально приближенного к реальному веб-приложению. Это кабинет в страховой компании, который отображает профиль пользователя, страховые случаи, предлагаемые тарифы страхования и инструменты для работы с этими данными.
В примере будет реализована лишь малая часть функционала, но по ней можно понять где и как располагать остальной функционал. Начинать создавать приложение начнем со слоя Controller, а View слой подключим в самом конце. А по ходу создания рассмотрим каждый слой детальнее.
Паттерн Controller
Controller — отвечает за взаимодействие пользователя с приложением. Это может быть клик по кнопке на веб странице, настольном приложении, мобильном приложении, или ввод команды в консоли линукса, или сетевой запрос, или любое другое IO событие приходящее в приложение.
Самый простой контроллер в чистой архитектуре выглядит следующим образом:
Его задача получить от пользователя событие и запустить бизнес процессы. В идеальном случае Controller ничего не знает про View, и тогда его можно переиспользовать между платформами, например Web, React-Native или Electron.
А теперь давайте напишем контроллер для нашего приложения. Его задача получить профиль пользователя, имеющиеся тарифы и предложить наилучший тариф пользователю:
У нас получился обычной контроллер без чистой архитектуры, если отнаследовать его от React.Component то получим рабочий компонент с логикой. Так пишут очень много разработчиков веб-приложений, но у такого подхода есть много существенных недостатков. Главный из которых невозможность переиспользования логики между компонентами. Ведь рекомендуемый тариф может выводиться не только в личном кабинете, но и на лендинге и множестве других мест для привлечения клиента к услуге.
Для того что бы иметь возможность переиспользовать логику между компонентами ее необходимо вынести в специальный слой, который называется Service.
Паттерн Service
Service — отвечает за всю бизнес логику приложения. Если Controller’у понадобилось получить, обработать, отправить какие то данные — он делает это через Service. Если нескольким контроллерам понадобилась одна и та же логика, они работают с Service. Но сам слой Service ничего не должен знать о слое Controller и View и окружении в котором он работает.
Давайте вынесем логику из контроллера в сервис и внедрим сервис в контроллер:
Теперь если нескольким контроллерам понадобиться получить профиль пользователя или тарифы, они смогу переиспользовать одну и туже логику из сервисов. В сервисах главное не забывать про SOLID принципы и что каждый сервис отвечает за свою зону ответственности. В данном случае один сервис отвечает за работу с профилем пользователя, а другой сервис за работу с тарифами.
Но что делать если источник данных поменяется, например fetch может смениться на websocket или grps или базу данных, а реальные данные понадобиться заменить тестовыми? И вообще зачем бизнес логике что то знать о источнике данных? Для решениях этих проблем существует слой Repository.
Паттерн Repository
Repository — отвечает за общение с хранилищем данных. В качестве хранилища может выступать сервер, база данных, память, localstorage, sessionstorage или любое другое хранилище. Его задача абстрагировать слой Service от конкретной реализации хранилища.
Давайте вынесем сетевые запросы из сервисов в репозитории, контроллер при этом не меняем:
Теперь достаточно один раз написать запрос к данным и любой сервис сможет переиспользовать этот запрос. Позже мы рассмотрим пример как переопределить репозиторий не трогая код сервиса и внедрить моковый репозиторий на время тестирования.
В сервисе UserProfilService может показаться что он не нужен и контроллер может напрямую обратиться к репозиторию за данными, но это не так. В любой момент в бизнес слое могут появиться или измениться требования, может потребоваться дополнительный запрос или обогатить данные. Поэтому даже когда в слое сервиса нету логики цепочка Controller — Service — Repository должна сохраняться. Это вклад в ваше завтра.
Настало время разобраться что за заданные получает репозиторий, корректные ли они вообще. За это отвечает слой Models.
Модели: DTO, Entities, ViewModels
Models — отвечает за описание структур с которыми работает приложение. Такое описание очень помогает новым разработчикам проекта понять с чем работает приложение. Кроме того его очень удобно использовать для построения баз данных или проведений валидаций данных хранящихся в модели.
Добавим в приложение модель профиля пользователя и другие модели, и сообщим остальным слоям что теперь мы работаем не с абстрактным объектом, а с вполне конкретным профилем:
Общие архитектуры веб-приложений
«Если вы считаете хорошую архитектуру слишком дорогой, попробуйте использовать плохую». — Брайан Фут (Brian Foote) и Джозеф Йодер (Joseph Yoder)
Что собой представляет монолитное приложение?
Монолитное приложение полностью замкнуто в контексте поведения. Во время работы оно может взаимодействовать с другими службами или хранилищами данных, однако основа его поведения реализуется в собственном процессе, а все приложение обычно развертывается как один элемент. Для горизонтального масштабирования такое приложение обычно целиком дублируется на нескольких серверах или виртуальных машинах.
Комплексные приложения
Архитектура приложения содержит как минимум один проект. В таком случае вся логика приложения заключена в одном проекте, компилируется в одну сборку и развертывается как один элемент.
Любой создаваемый в Visual Studio или из командной строки проект ASP.NET Core изначально будет представлять собой комплексный монолитный проект. В нем будет заключено все поведение приложения, включая презентацию данных, бизнес-логику и логику доступа к данным. На рис. 5-1 показана файловая структура приложения, состоящего из одного проекта.
Рис. 5-1. Приложение ASP.NET Core, состоящее из одного проекта.
В сценарии с одним проектом разделение задач реализуется с помощью папок. Используемый по умолчанию шаблон включает отдельные папки для обязанностей шаблона MVC (модели, представления и контроллеры), а также дополнительные папки для данных и служб. При такой организации детали презентации данных в максимально возможной степени размещаются в папке представлений (Views), а детали реализации доступа к данным должны быть ограничены классами, содержащимися в папке данных (Data). Бизнес-логика при этом размещается в службах и классах, находящихся в папке моделей (Models).
Несмотря на свою простоту, монолитное решение с одним проектом имеет определенные недостатки. По мере увеличения размера и сложности проекта будет расти число файлов и папок. Задачи, связанные с пользовательским интерфейсом (модели, представления, контроллеры), размещаются в разных папках, которые не упорядочены по алфавиту. С добавлением в отдельные папки конструкций уровня пользовательского интерфейса, например фильтров или связывателей модели, ситуация только ухудшается. Бизнес-логика теряется в папках моделей (Models) и служб (Services), в результате чего невозможно четко определить, какие классы в каких папках должны зависеть от других классов. Подобная неэффективная организация на уровне проекта часто приводит к получению плохо структурированного кода.
Для решения подобных проблем приложения часто организуются в виде решений, состоящих из множества проектов, где каждый проект размещается в отдельном слое приложения.
Что представляют собой слои?
По мере увеличения сложности приложения для эффективного управления им может применяться разбиение по обязанностям и задачам. Такой подход соответствует принципу разделения задач и помогает сохранить организацию расширяющейся базы кода, благодаря чему разработчики могут быстро определять, где именно реализованы определенные функции. Многослойная архитектура имеет также целый ряд других преимуществ.
Благодаря упорядочению кода с помощью слоев общие низкоуровневые функции могут многократно использоваться по всему приложению. Это крайне важно, поскольку такой подход требует меньшего объема кода и, за счет стандартизации приложения на уровне одной реализации, соответствует принципу «Не повторяйся».
В приложениях с многослойной архитектурой могут устанавливаться ограничения на взаимодействие между слоями. Такая архитектура помогает реализовать инкапсуляцию. При изменении или замене слоя будут затронуты только те слои, которые работают непосредственно с ним. Ограничивая зависимости слоев друг от друга, можно уменьшить последствия внесения изменений, в результате чего единичное изменение не будет влиять на все приложение.
Применение слоев (и инкапсуляция) позволяет заметно упростить замену функциональных возможностей в рамках приложения. Например, приложение может изначально использовать собственную базу данных SQL Server для сохраняемости, а впоследствии перейти на стратегию сохранения состояния на основе облака или веб-API. Если в приложении надлежащим образом инкапсулирована реализация сохраняемости на логическом слое, этот слой SQL Server может быть заменен новым, где будет реализовываться тот же открытый интерфейс.
Помимо возможности замены реализаций в связи с последующими изменениями, применение слоев в приложении также позволяет менять реализации в целях тестирования. Вместо написания тестов, которые применяются к слоям реальных данных или пользовательского интерфейса приложения, во время тестирования они заменяются фиктивными реализациями, которые демонстрируют известную реакцию на запросы. Как правило, это значительно упрощает написание тестов и ускоряет их выполнение по сравнению с тестированием в реальной инфраструктуре приложения.
Разделение на логические слои широко распространено и помогает упорядочить код приложений предприятия. Сделать это можно несколькими способами.
Слои обеспечивают логический уровень разделения в приложении. Если логика приложения физически распределена между несколькими серверами или процессами, такие раздельные физические целевые объекты развертывания называются уровнями. Таким образом, не только возможно, но и широко распространено развертывание N-слойных приложений на одном уровне.
Традиционные приложения с N-слойной архитектурой
Общепринятая организация логики приложения по слоям показана на рис. 5-2.
Рис. 5-2. Слои типового приложения.
Как правило, в приложении определяются слои пользовательского интерфейса, бизнес-логики и доступа к данным. В рамках такой архитектуры пользователи выполняют запросы через слой пользовательского интерфейса, который взаимодействует только со слоем бизнес-логики. Слой бизнес-логики, в свою очередь, может вызывать слой доступа к данным для обработки запросов. Слой пользовательского интерфейса не должен выполнять запросы напрямую к слою доступа к данным и какими-либо другими способами напрямую взаимодействовать с функциями сохраняемости. Аналогичным образом, слой бизнес-логики должен взаимодействовать с функциями сохраняемости только через слой доступа к данным. Таким образом, для каждого слоя четко определена своя обязанность.
Одним из недостатков традиционного многослойного подхода является то, что обработка зависимостей во время компиляции осуществляется сверху вниз. Это значит, что слой пользовательского интерфейса зависит от слоя бизнес-логики, который, в свою очередь, зависит от слоя доступа к данным. Это значит, что слой бизнес-логики, который обычно содержит ключевые функции приложения, зависит от деталей реализации доступа к данным (и зачастую от наличия самой базы данных). Тестирование бизнес-логики в такой архитектуре зачастую затруднено и требует наличия тестовой базы данных. Для решения этой проблемы может применяться принцип инверсии зависимостей, как описывается в следующем разделе.
На рис. 5-3 показан пример решения, в котором приложение разделено на три проекта (или слоя) в соответствии с определенными обязанностями.
Рис. 5-3. Простое монолитное приложение, состоящее из трех проектов.
Несмотря на то, что в целях упорядочения в этом приложении используется несколько проектов, оно по-прежнему развертывается как единый элемент, и его клиенты взаимодействуют с ним как с одним веб-приложением. Это позволяет реализовать крайне простой процесс развертывания. На рис. 5-4 показано, как такое приложение можно разместить с использованием Azure.
Рис. 5-4. Простое развертывание веб-приложения Azure
По мере развития приложения могут потребоваться более сложные и надежные решения для развертывания. На рис. 5-5 показан пример более сложного плана развертывания, который поддерживает дополнительные возможности.
Рис. 5-5. Развертывание веб-приложения в службе приложений Azure
Разбиение этого проекта на несколько проектов на основе обязанностей позволяет повысить удобство поддержки приложения.
Такой элемент поддерживает вертикальное и горизонтальное масштабирование, что позволяет использовать преимущества облачного масштабирования по запросу. Под вертикальным масштабированием понимается увеличение числа ЦП, объема памяти, места на диске и других ресурсов на серверах, где размещается приложение. Горизонтальное масштабирование заключается в добавлении дополнительных экземпляров таких физических серверов, виртуальных машин или контейнеров. Если приложение размещается на нескольких экземплярах, для распределения запросов между экземплярами приложения используется система балансировки нагрузки.
Самый простой подход к масштабированию веб-приложения в Azure заключается в ручной настройке масштабирования в плане службы приложений для приложения. На рис. 5-6 показан экран панели мониторинга Azure, предназначенный для настройки числа экземпляров, обслуживающих приложение.
Рис. 5-6. Масштабирование плана службы приложений в Azure.
Чистая архитектура
Приложения, использующие принципы инверсии зависимостей и проблемно-ориентированного проектирования, имеют схожую архитектуру. На протяжении многих лет она носила самые разные названия. Сначала это была шестигранная архитектура, на смену которой пришла архитектура портов и адаптеров. На современном этапе она называется многослойной или чистой архитектурой. В этой электронной книге используется термин «чистая архитектура».
Эталонное приложение eShopOnWeb использует подход на основе чистой архитектуры для организации кода в проекты. Вы можете найти шаблон решения, который можно использовать в качестве отправной точки для собственных решений ASP.NET Core в репозитории ardalis/cleanarchitecture на GitHub.
В рамках чистой архитектуры центральным элементом приложения являются его бизнес-логика и модель. В этом случае бизнес-логика не зависит от доступа к данным или другим инфраструктурам, то есть стандартная зависимость инвертируется: инфраструктура и детали реализации зависят от ядра приложения. Эта функциональность достигается путем определения абстракций или интерфейсов в ядре приложения, которые реализуются типами, определенными в слое инфраструктуры. Такую архитектуру обычно рисуют в виде серии окружностей с общим центром, которая внешне напоминает срез луковицы. На рис. 5-7 показан пример такого стиля представления архитектуры.
Рис. 5-7. Чистая архитектура (многослойное представление)
На этой схеме зависимости направлены из самой внутренней окружности. Ядро приложения называется так потому, что находится в самом центре этой схемы. Как видно на схеме, ядро приложения не имеет зависимостей от других слоев приложения. Сущности и интерфейсы приложения находятся в самом центре. Сразу после них, но все еще в пределах ядра приложения, расположены доменные службы, которые обычно реализуют интерфейсы, определенные во внутренней окружности. За пределами ядра приложения располагаются слои пользовательского интерфейса и инфраструктуры, которые зависят от ядра приложения, но не друг от друга (обязательно).
На рис. 5-8 показана более привычная горизонтальная схема слоев, которая лучше отражает зависимости между слоем пользовательского интерфейса и другими слоями.
Рис. 5-8. Чистая архитектура (горизонтальное представление слоев)
Обратите внимание, что сплошные стрелки соответствуют зависимостям времени компиляции, а пунктирные — зависимостям, которые существуют только во время выполнения. В рамках чистой архитектуры слой пользовательского интерфейса работает с интерфейсами, которые определены в ядре приложения во время компиляции, и в идеальном случае не должен знать ничего о типах реализации, определенных в слое инфраструктуры. Тем не менее во время выполнения эти типы реализации необходимы для выполнения приложения, поэтому они должны существовать и быть привязаны к интерфейсам ядра приложения посредством внедрения зависимостей.
На рис. 5-9 показано более подробное представление архитектуры приложения ASP.NET Core, построенного с соблюдением этих рекомендаций.
Рис. 5-9. Схема чистой архитектуры ASP.NET Core.
Поскольку ядро приложения не зависит от инфраструктуры, для этого слоя легко писать автоматические модульные тесты. На рис. 5-10 и 5-11 показано, как эти тесты вписываются в такую архитектуру.
Рис. 5-10. Изолированное модульное тестирование ядра приложения.
Рис. 5-11. Интеграционное тестирование реализаций инфраструктуры с внешними зависимостями.
Поскольку слой пользовательского интерфейса не имеет прямых зависимостей от типов, определенных в проекте инфраструктуры, будет так же просто менять реализации в целях тестирования или в случае изменения требований к приложению. ASP.NET Core предлагает встроенную поддержку внедрения зависимостей, в связи с чем такая архитектура представляет собой оптимальный подход к структурированию нетривиальных монолитных приложений.
Для монолитных приложений проекты ядра приложения, инфраструктуры и пользовательского интерфейса выполняются как единое приложение. Во время выполнения архитектура приложения будет выглядеть так, как показано на рис. 5-12.
Рис. 5-12. Пример архитектуры приложения ASP.NET Core во время выполнения.
Упорядочение кода в рамках чистой архитектуры
В решении с чистой архитектурой для каждого проекта четко определены обязанности. Фактически, каждому проекту будут принадлежать определенные типы, а в проектах будут представлены соответствующие этим типам папки.
Ядро приложения
Ядро приложения содержит бизнес-модель, которая включает в себя сущности, службы и интерфейсы. Такие интерфейсы включают абстракции для операций, которые будут выполняться с использованием архитектуры, включая операции доступа к данным или файловой системе, сетевые вызовы и т. д. В некоторых случаях службы или интерфейсы, определенные в этом слое, должны работать с типами, не являющимися типами сущностей, которые не имеют зависимостей от пользовательского интерфейса или инфраструктуры. Они могут определяться как простые объекты передачи данных.
Типы ядра приложения
Инфраструктура
Как правило, проект инфраструктуры включает реализацию доступа к данным. В типовом веб-приложении ASP.NET Core эта реализация включает Entity Framework (EF) DbContext, любые определенные объекты Migration EF Core, а также классы реализации доступа к данным. Наиболее распространенный подход к абстрагированию кода реализации доступа к данным заключается в использовании конструктивного шаблона репозитория.
Помимо реализации доступа к данным, проект инфраструктуры должен также включать реализации служб, которые должны взаимодействовать с инфраструктурными задачами. Эти службы должны реализовывать интерфейсы, определенные в ядре приложения. Таким образом, инфраструктура должна содержать ссылку на проект ядра приложения.
Типы инфраструктуры
Уровень пользовательского интерфейса
Слой пользовательского интерфейса в приложении MVC ASP.NET Core выступает в качестве точки входа для приложения. Этот проект должен ссылаться на слой ядра приложения, а его типы должны взаимодействовать с инфраструктурой строго через интерфейсы, определенные в ядре приложения. В слое пользовательского интерфейса не должны разрешаться прямое создание экземпляров для типов слоя инфраструктуры, а также их статические вызовы.
Типы слоев пользовательского интерфейса
Класс Startup отвечает за настройку приложений и запись типов реализации в интерфейсы, обеспечивая корректную работу внедрения зависимостей во время выполнения.
Чтобы привязать внедрение зависимостей в ConfigureServices в файле Startup.cs проекта пользовательского интерфейса, в этом проекте может потребоваться ссылка на проект инфраструктуры. Эту зависимость можно исключить. Проще всего это сделать с помощью настраиваемого контейнера внедрения зависимостей. В рамках этого примера применяется простейший подход, при котором разрешаются ссылки из проекта пользовательского интерфейса на проект инфраструктуры.
Монолитные приложения и контейнеры
Вы можете создать одно монолитное веб-приложение или службу и развернуть их как контейнер. В рамках приложения монолитность может не соблюдаться, однако будет реализована организация на основе нескольких библиотек, компонентов или слоев. Внешне оно будет представлять собой единый контейнер — единый процесс, единое веб-приложение или единую службу.
Для управления этой моделью вы развертываете один контейнер, представляющий собой приложение. Для увеличения масштаба просто добавьте дополнительные копии с подсистемой балансировки нагрузки спереди. Управлять одним развертыванием в одном контейнере или виртуальной машине гораздо проще.
Вы можете включить в один контейнер несколько компонентов, библиотек или внутренних слоев, как показано на рис. 5-13. Такой монолитный шаблон может конфликтовать с принципом контейнера: «контейнер выполняет одно дело и в одном процессе«.
Недостаток этого подхода становится очевидным, когда приложение разрастается и его необходимо масштабировать. Если масштабируется приложение целиком, все получится. Но в большинстве случаев необходимо масштабировать всего несколько частей приложения, пока другие компоненты работают нормально.
В примере приложения для электронной торговли, вероятнее всего, потребуется масштабирование компонента со сведениями о товарах. Клиенты чаще просматривают товары, чем приобретают их. Клиенты чаще складывают товары в корзину, чем оплачивают их. Не так много клиентов пишут комментарии или просматривают историю покупок. И у вас скорее всего может быть лишь несколько сотрудников в одном регионе, которые управляют содержимым и маркетинговыми кампаниями. При масштабировании монолитных решений весь код развертывается многократно.
Помимо того, что необходимо масштабировать все компоненты, изменения в одном компоненте требуют полного повторного тестирования всего приложения и полного повторного развертывания всех его экземпляров.
Монолитный подход нашел широкое распространение и используется многими организациями при разработке архитектуры. Во многих случаях это позволяет добиться желаемых результатов, однако иногда организация сталкивается с достигнутыми ограничениями. Во многих организациях приложения строились по такой модели, поскольку несколько лет назад с помощью существующих инструментов и инфраструктуры слишком сложно было создавать архитектуры, ориентированные на службы (SOA), и проблем не возникало, пока приложение не начинало разрастаться. Если ваша организация столкнулась с ограничениями монолитного подхода, следующим логичным шагом может стать разбиение приложения для более эффективного использования контейнеров и микрослужб.
Монолитные приложения в Microsoft Azure можно развертывать с использованием выделенных виртуальных машин для каждого экземпляра. С помощью масштабируемых наборов виртуальных машин Azure можно легко масштабировать виртуальные машины. Службы приложений Azure также могут выполнять монолитные приложения и легко масштабировать экземпляры, и вам не придется управлять виртуальными машинами. Службы приложений Azure также могут выполнять отдельные экземпляры контейнеров Docker, упрощая развертывание. С помощью Docker вы можете развернуть одну виртуальную машину на узле Docker и выполнять на ней несколько экземпляров. Для управления масштабированием можно использовать систему балансировки Azure, как показано на рис. 5-14.
Развертыванием на различных узлах можно управлять с помощью традиционных методов развертывания. Узлами Docker можно управлять с помощью вводимых вручную команд вида docker run или автоматизированно, например с помощью конвейеров непрерывной поставки (CD).
Развертывание монолитного приложения в контейнере
Использование контейнеров для управления развертываниями монолитных приложений имеет свои преимущества. Масштабировать экземпляры контейнера гораздо быстрее и проще, чем развертывать дополнительные виртуальные машины. Даже при использовании масштабируемых наборов виртуальных машин применяется основанный на экземплярах подход. При развертывании в виде экземпляров приложения управление конфигурацией приложения осуществляется в составе виртуальной машины.
Так как контейнеры по своей природе являются неизменяемыми, вам не придется беспокоиться о возможности повреждения виртуальной машины, когда скрипты обновления не учитывают некоторые оставшиеся на диске конфигурации или файлы.
Контейнеры Docker можно использовать для монолитного развертывания простых веб-приложений. Это позволяет оптимизировать конвейеры непрерывной интеграции и непрерывного развертывания, а также добиться успешного развертывания в рабочей среде. Вам больше не придется гадать, почему решение, выполняющееся на вашем компьютере, не выполняется в рабочей среде.
Архитектура на основе микрослужб имеет много преимуществ, но обратной стороной является повышение сложности. В некоторых случаях затраты перевешивают преимущества, и лучше прибегнуть к монолитному развертыванию приложения в одном контейнере или буквально нескольких.
Монолитное приложение может быть непросто разделить на отдельные микрослужбы. Микрослужбы должны работать независимо друг от друга для повышения отказоустойчивости приложения. Если приложение невозможно разложить на независимые функциональные составляющие, то его разделение лишь увеличит сложность.
Приложению пока может не требоваться независимое масштабирование компонентов. Многие приложения, когда им требуется использовать более одного экземпляра, относительно легко клонируют весь экземпляр. Дополнительный труд по разделению приложений на отдельные службы предоставляет минимум преимуществ, тогда как масштабирование полноценных экземпляров приложения — это простое и экономичное решение.
На ранних этапах развертывания приложения может отсутствовать ясное представление о том, где пролегают границы между функциональными областями. В процессе разработки продукта, обладающего минимальным необходимым набором возможностей, его естественное разделение на части может быть не очевидным. Некоторые из этих условий могут быть временными. Вы можете сначала создать монолитное приложение, а в дальнейшем отделить некоторые компоненты для разработки и развертывания в качестве микрослужб. Другие условия могут быть неотъемлемыми особенностями приложения. Это означает, что приложение в принципе невозможно разделить на несколько микрослужб.
Разделение приложения на множество отдельных процессов также приводит к накладным расходам. При разделении компонента на несколько процессов повышается сложность. Усложняются протоколы обмена данными. Вместо вызовов методов необходимо использовать асинхронное взаимодействие между службами. При переходе на архитектуру микрослужб необходимо добавить множество стандартных блоков, реализованных в версии приложения eShopOnContainers на основе микрослужб: обработку шины событий, отказоустойчивость и повторную отправку сообщений, итоговую согласованность и многое другое.
Во-первых, контейнерное развертывание означает, что каждый экземпляр приложения выполняется в одной и той же среде. Это относится и к среде разработки, в которой проводятся начальные этапы тестирования и разработки. Команда разработчиков может запускать приложение в контейнерной среде, которая аналогична рабочей.
Кроме того, контейнерные приложения обеспечивают более экономичное горизонтальное масштабирование. Контейнерная среда позволяет эффективнее организовывать совместное использование ресурсов, чем традиционные среды виртуальных машин.
Наконец, помещая приложение в контейнеры, вы разделяете бизнес-логику и сервер хранилища. По мере масштабирования приложения все контейнеры будут использовать один физический носитель данных. В качестве хранилища, как правило, используется сервер высокой доступности с базой данных SQL Server.
Поддержка Docker
Вы можете использовать Visual Studio 2017, чтобы добавить поддержку Docker в существующее приложение, щелкнув проект в обозревателе решений правой кнопкой мыши и выбрав Добавить > Поддержка Docker. Таким образом вы добавите необходимые файлы и внесете изменения в проект для их использования. В текущем примере eShopOnWeb эти файлы уже есть.
Файл docker-compose.yml на уровне решения содержит сведения о том, какие образы необходимо создать и какие контейнеры запустить. Этот файл позволяет использовать команду docker-compose для запуска нескольких приложений одновременно. В этом случае он запускает только веб-проект. Вы также можете с его помощью настроить зависимости, например отдельный контейнер базы данных.
Устранение неполадок с Docker
Обратите внимание, что запущенные контейнеры Docker могут быть привязаны к портам, которые в противном случае вы могли бы использовать в среде разработки. При попытке запустить или выполнить отладку приложения через порт, связанный с контейнером Docker, возникнет ошибка с сообщением о том, что сервер не может выполнить привязку к этому порту. Проблемы будет решена, если вы остановите контейнер.
Если вы хотите добавить поддержку Docker в приложение с помощью Visual Studio, убедитесь, что Docker Desktop при этом запущен. Если при запуске мастера средство Docker Desktop не выполняется, мастер будет работать неправильно. Кроме того, мастер проверяет выбранные контейнеры, чтобы правильно реализовать поддержку Docker. Чтобы добавить поддержку контейнеров Windows, необходимо запускать мастер при запущенном инструменте Docker Desktop с настроенными контейнерами Windows. Чтобы добавить поддержку контейнеров Linux, запускайте мастер при запущенном инструменте Docker с настроенными контейнерами Linux.