Общие архитектуры веб-приложенийCommon web application architectures

"Если вы считаете хорошую архитектуру слишком дорогой, попробуйте использовать плохую"."If you think good architecture is expensive, try bad architecture." — Брайан Фут (Brian Foote) и Джозеф Йодер (Joseph Yoder)- Brian Foote and Joseph Yoder

Большинство традиционных приложений .NET развертывается в виде одного элемента, соответствующего исполняемому файлу, или одного веб-приложения, выполняющегося в домене приложений служб IIS.Most traditional .NET applications are deployed as single units corresponding to an executable or a single web application running within a single IIS appdomain. Это простейшая модель развертывания, которая оптимально подходит для множества внутренних и небольших общедоступных приложений.This approach is the simplest deployment model and serves many internal and smaller public applications very well. Тем не менее даже в такой простой модели развертывания большинство бизнес-приложений использует преимущества логического разделения на слои.However, even given this single unit of deployment, most non-trivial business applications benefit from some logical separation into several layers.

Что собой представляет монолитное приложение?What is a monolithic application?

Монолитное приложение полностью замкнуто в контексте поведения.A monolithic application is one that is entirely self-contained, in terms of its behavior. Во время работы оно может взаимодействовать с другими службами или хранилищами данных, однако основа его поведения реализуется в собственном процессе, а все приложение обычно развертывается как один элемент.It may interact with other services or data stores in the course of performing its operations, but the core of its behavior runs within its own process and the entire application is typically deployed as a single unit. Для горизонтального масштабирования такое приложение обычно целиком дублируется на нескольких серверах или виртуальных машинах.If such an application needs to scale horizontally, typically the entire application is duplicated across multiple servers or virtual machines.

Комплексные приложенияAll-in-one applications

Архитектура приложения содержит как минимум один проект.The smallest possible number of projects for an application architecture is one. В таком случае вся логика приложения заключена в одном проекте, компилируется в одну сборку и развертывается как один элемент.In this architecture, the entire logic of the application is contained in a single project, compiled to a single assembly, and deployed as a single unit.

Любой создаваемый в Visual Studio или из командной строки проект ASP.NET Core изначально будет представлять собой комплексный монолитный проект.A new ASP.NET Core project, whether created in Visual Studio or from the command line, starts out as a simple "all-in-one" monolith. В нем будет заключено все поведение приложения, включая презентацию данных, бизнес-логику и логику доступа к данным.It contains all of the behavior of the application, including presentation, business, and data access logic. На рис. 5-1 показана файловая структура приложения, состоящего из одного проекта.Figure 5-1 shows the file structure of a single-project app.

Приложение ASP.NET Core, состоящее из одного проекта

Рис. 5-1.Figure 5-1. Приложение ASP.NET Core, состоящее из одного проекта.A single project ASP.NET Core app.

В сценарии с одним проектом разделение задач реализуется с помощью папок.In a single project scenario, separation of concerns is achieved through the use of folders. Используемый по умолчанию шаблон включает отдельные папки для обязанностей шаблона MVC (модели, представления и контроллеры), а также дополнительные папки для данных и служб.The default template includes separate folders for MVC pattern responsibilities of Models, Views, and Controllers, as well as additional folders for Data and Services. При такой организации детали презентации данных в максимально возможной степени размещаются в папке представлений (Views), а детали реализации доступа к данным должны быть ограничены классами, содержащимися в папке данных (Data).In this arrangement, presentation details should be limited as much as possible to the Views folder, and data access implementation details should be limited to classes kept in the Data folder. Бизнес-логика при этом размещается в службах и классах, находящихся в папке моделей (Models).Business logic should reside in services and classes within the Models folder.

Несмотря на свою простоту, монолитное решение с одним проектом имеет определенные недостатки.Although simple, the single-project monolithic solution has some disadvantages. По мере увеличения размера и сложности проекта будет расти число файлов и папок.As the project's size and complexity grows, the number of files and folders will continue to grow as well. Задачи, связанные с пользовательским интерфейсом (модели, представления, контроллеры), размещаются в разных папках, которые не упорядочены по алфавиту.User interface (UI) concerns (models, views, controllers) reside in multiple folders, which aren't grouped together alphabetically. С добавлением в отдельные папки конструкций уровня пользовательского интерфейса, например фильтров или связывателей модели, ситуация только ухудшается.This issue only gets worse when additional UI-level constructs, such as Filters or ModelBinders, are added in their own folders. Бизнес-логика теряется в папках моделей (Models) и служб (Services), в результате чего невозможно четко определить, какие классы в каких папках должны зависеть от других классов.Business logic is scattered between the Models and Services folders, and there's no clear indication of which classes in which folders should depend on which others. Подобная неэффективная организация на уровне проекта часто приводит к получению плохо структурированного кода.This lack of organization at the project level frequently leads to spaghetti code.

Для решения подобных проблем приложения часто организуются в виде решений, состоящих из множества проектов, где каждый проект размещается в отдельном слое приложения.To address these issues, applications often evolve into multi-project solutions, where each project is considered to reside in a particular layer of the application.

Что представляют собой слои?What are layers?

По мере увеличения сложности приложения для эффективного управления им может применяться разбиение по обязанностям и задачам.As applications grow in complexity, one way to manage that complexity is to break up the application according to its responsibilities or concerns. Такой подход соответствует принципу разделения задач и помогает сохранить организацию расширяющейся базы кода, благодаря чему разработчики могут быстро определять, где именно реализованы определенные функции.This approach follows the separation of concerns principle and can help keep a growing codebase organized so that developers can easily find where certain functionality is implemented. Многослойная архитектура имеет также целый ряд других преимуществ.Layered architecture offers a number of advantages beyond just code organization, though.

Благодаря упорядочению кода с помощью слоев общие низкоуровневые функции могут многократно использоваться по всему приложению.By organizing code into layers, common low-level functionality can be reused throughout the application. Это крайне важно, поскольку такой подход требует меньшего объема кода и, за счет стандартизации приложения на уровне одной реализации, соответствует принципу "Не повторяйся".This reuse is beneficial because it means less code needs to be written and because it can allow the application to standardize on a single implementation, following the don't repeat yourself (DRY) principle.

В приложениях с многослойной архитектурой могут устанавливаться ограничения на взаимодействие между слоями.With a layered architecture, applications can enforce restrictions on which layers can communicate with other layers. Такая архитектура помогает реализовать инкапсуляцию.This architecture helps to achieve encapsulation. При изменении или замене слоя будут затронуты только те слои, которые работают непосредственно с ним.When a layer is changed or replaced, only those layers that work with it should be impacted. Ограничивая зависимости слоев друг от друга, можно уменьшить последствия внесения изменений, в результате чего единичное изменение не будет влиять на все приложение.By limiting which layers depend on which other layers, the impact of changes can be mitigated so that a single change doesn't impact the entire application.

Применение слоев (и инкапсуляция) позволяет заметно упростить замену функциональных возможностей в рамках приложения.Layers (and encapsulation) make it much easier to replace functionality within the application. Например, приложение может изначально использовать собственную базу данных SQL Server для сохраняемости, а впоследствии перейти на стратегию сохранения состояния на основе облака или веб-API.For example, an application might initially use its own SQL Server database for persistence, but later could choose to use a cloud-based persistence strategy, or one behind a web API. Если в приложении надлежащим образом инкапсулирована реализация сохраняемости на логическом слое, этот слой SQL Server может быть заменен новым, где будет реализовываться тот же открытый интерфейс.If the application has properly encapsulated its persistence implementation within a logical layer, that SQL Server-specific layer could be replaced by a new one implementing the same public interface.

Помимо возможности замены реализаций в связи с последующими изменениями, применение слоев в приложении также позволяет менять реализации в целях тестирования.In addition to the potential of swapping out implementations in response to future changes in requirements, application layers can also make it easier to swap out implementations for testing purposes. Вместо написания тестов, которые применяются к слоям реальных данных или пользовательского интерфейса приложения, во время тестирования они заменяются фиктивными реализациями, которые демонстрируют известную реакцию на запросы.Instead of having to write tests that operate against the real data layer or UI layer of the application, these layers can be replaced at test time with fake implementations that provide known responses to requests. Как правило, это значительно упрощает написание тестов и ускоряет их выполнение по сравнению с тестированием в реальной инфраструктуре приложения.This approach typically makes tests much easier to write and much faster to run when compared to running tests against the application's real infrastructure.

Разделение на логические слои широко распространено и помогает упорядочить код приложений предприятия. Сделать это можно несколькими способами.Logical layering is a common technique for improving the organization of code in enterprise software applications, and there are several ways in which code can be organized into layers.

Примечание

Слои обеспечивают логический уровень разделения в приложении.Layers represent logical separation within the application. Если логика приложения физически распределена между несколькими серверами или процессами, такие раздельные физические целевые объекты развертывания называются уровнями.In the event that application logic is physically distributed to separate servers or processes, these separate physical deployment targets are referred to as tiers. Таким образом, не только возможно, но и широко распространено развертывание N-слойных приложений на одном уровне.It's possible, and quite common, to have an N-Layer application that is deployed to a single tier.

Традиционные приложения с N-слойной архитектуройTraditional "N-Layer" architecture applications

Общепринятая организация логики приложения по слоям показана на рис. 5-2.The most common organization of application logic into layers is shown in Figure 5-2.

Слои типового приложения

Рис. 5-2.Figure 5-2. Слои типового приложения.Typical application layers.

Как правило, в приложении определяются слои пользовательского интерфейса, бизнес-логики и доступа к данным.These layers are frequently abbreviated as UI, BLL (Business Logic Layer), and DAL (Data Access Layer). В рамках такой архитектуры пользователи выполняют запросы через слой пользовательского интерфейса, который взаимодействует только со слоем бизнес-логики.Using this architecture, users make requests through the UI layer, which interacts only with the BLL. Слой бизнес-логики, в свою очередь, может вызывать слой доступа к данным для обработки запросов.The BLL, in turn, can call the DAL for data access requests. Слой пользовательского интерфейса не должен выполнять запросы напрямую к слою доступа к данным и какими-либо другими способами напрямую взаимодействовать с функциями сохраняемости.The UI layer shouldn't make any requests to the DAL directly, nor should it interact with persistence directly through other means. Аналогичным образом, слой бизнес-логики должен взаимодействовать с функциями сохраняемости только через слой доступа к данным.Likewise, the BLL should only interact with persistence by going through the DAL. Таким образом, для каждого слоя четко определена своя обязанность.In this way, each layer has its own well-known responsibility.

Одним из недостатков традиционного многослойного подхода является то, что обработка зависимостей во время компиляции осуществляется сверху вниз.One disadvantage of this traditional layering approach is that compile-time dependencies run from the top to the bottom. Это значит, что слой пользовательского интерфейса зависит от слоя бизнес-логики, который, в свою очередь, зависит от слоя доступа к данным.That is, the UI layer depends on the BLL, which depends on the DAL. Это значит, что слой бизнес-логики, который обычно содержит ключевые функции приложения, зависит от деталей реализации доступа к данным (и зачастую от наличия самой базы данных).This means that the BLL, which usually holds the most important logic in the application, is dependent on data access implementation details (and often on the existence of a database). Тестирование бизнес-логики в такой архитектуре зачастую затруднено и требует наличия тестовой базы данных.Testing business logic in such an architecture is often difficult, requiring a test database. Для решения этой проблемы может применяться принцип инверсии зависимостей, как описывается в следующем разделе.The dependency inversion principle can be used to address this issue, as you'll see in the next section.

На рис. 5-3 показан пример решения, в котором приложение разделено на три проекта (или слоя) в соответствии с определенными обязанностями.Figure 5-3 shows an example solution, breaking the application into three projects by responsibility (or layer).

Простое монолитное приложение, состоящее из трех проектов

Рис. 5-3.Figure 5-3. Простое монолитное приложение, состоящее из трех проектов.A simple monolithic application with three projects.

Несмотря на то, что в целях упорядочения в этом приложении используется несколько проектов, оно по-прежнему развертывается как единый элемент, и его клиенты взаимодействуют с ним как с одним веб-приложением.Although this application uses several projects for organizational purposes, it's still deployed as a single unit and its clients will interact with it as a single web app. Это позволяет реализовать крайне простой процесс развертывания.This allows for very simple deployment process. На рис. 5-4 показано, как такое приложение можно разместить с использованием Azure.Figure 5-4 shows how such an app might be hosted using Azure.

Простое развертывание веб-приложения Azure

Рис. 5-4.Figure 5-4. Простое развертывание веб-приложения AzureSimple deployment of Azure Web App

По мере развития приложения могут потребоваться более сложные и надежные решения для развертывания.As application needs grow, more complex and robust deployment solutions may be required. На рис. 5-5 показан пример более сложного плана развертывания, который поддерживает дополнительные возможности.Figure 5-5 shows an example of a more complex deployment plan that supports additional capabilities.

Развертывание веб-приложения в службе приложений Azure

Рис. 5-5.Figure 5-5. Развертывание веб-приложения в службе приложений AzureDeploying a web app to an Azure App Service

Разбиение этого проекта на несколько проектов на основе обязанностей позволяет повысить удобство поддержки приложения.Internally, this project's organization into multiple projects based on responsibility improves the maintainability of the application.

Такой элемент поддерживает вертикальное и горизонтальное масштабирование, что позволяет использовать преимущества облачного масштабирования по запросу.This unit can be scaled up or out to take advantage of cloud-based on-demand scalability. Под вертикальным масштабированием понимается увеличение числа ЦП, объема памяти, места на диске и других ресурсов на серверах, где размещается приложение.Scaling up means adding additional CPU, memory, disk space, or other resources to the server(s) hosting your app. Горизонтальное масштабирование заключается в добавлении дополнительных экземпляров таких физических серверов, виртуальных машин или контейнеров.Scaling out means adding additional instances of such servers, whether these are physical servers, virtual machines, or containers. Если приложение размещается на нескольких экземплярах, для распределения запросов между экземплярами приложения используется система балансировки нагрузки.When your app is hosted across multiple instances, a load balancer is used to assign requests to individual app instances.

Самый простой подход к масштабированию веб-приложения в Azure заключается в ручной настройке масштабирования в плане службы приложений для приложения.The simplest approach to scaling a web application in Azure is to configure scaling manually in the application's App Service Plan. На рис. 5-6 показан экран панели мониторинга Azure, предназначенный для настройки числа экземпляров, обслуживающих приложение.Figure 5-6 shows the appropriate Azure dashboard screen to configure how many instances are serving an app.

Масштабирование плана службы приложений в Azure

Рис. 5-6.Figure 5-6. Масштабирование плана службы приложений в Azure.App Service Plan scaling in Azure.

Чистая архитектураClean architecture

Приложения, использующие принципы инверсии зависимостей и проблемно-ориентированного проектирования, имеют схожую архитектуру.Applications that follow the Dependency Inversion Principle as well as the Domain-Driven Design (DDD) principles tend to arrive at a similar architecture. На протяжении многих лет она носила самые разные названия.This architecture has gone by many names over the years. Сначала это была шестигранная архитектура, на смену которой пришла архитектура портов и адаптеров.One of the first names was Hexagonal Architecture, followed by Ports-and-Adapters. На современном этапе она называется многослойной или чистой архитектурой.More recently, it's been cited as the Onion Architecture or Clean Architecture. В этой электронной книге используется термин "чистая архитектура".The latter name, Clean Architecture, is used as the name for this architecture in this e-book.

Эталонное приложение eShopOnWeb использует подход на основе чистой архитектуры для организации кода в проекты.The eShopOnWeb reference application uses the Clean Architecture approach in organizing its code into projects. Вы можете найти шаблон решения, который можно использовать в качестве отправной точки для собственных решений ASP.NET Core в репозитории ardalis/cleanarchitecture на GitHub.You can find a solution template you can use as a starting point for your own ASP.NET Core on the ardalis/cleanarchitecture GitHub repository.

В рамках чистой архитектуры центральным элементом приложения являются его бизнес-логика и модель.Clean architecture puts the business logic and application model at the center of the application. В этом случае бизнес-логика не зависит от доступа к данным или другим инфраструктурам, то есть стандартная зависимость инвертируется: инфраструктура и детали реализации зависят от ядра приложения.Instead of having business logic depend on data access or other infrastructure concerns, this dependency is inverted: infrastructure and implementation details depend on the Application Core. Эта функциональность достигается путем определения абстракций или интерфейсов в ядре приложения, которые реализуются типами, определенными в слое инфраструктуры.This functionality is achieved by defining abstractions, or interfaces, in the Application Core, which are then implemented by types defined in the Infrastructure layer. Такую архитектуру обычно рисуют в виде серии окружностей с общим центром, которая внешне напоминает срез луковицы.A common way of visualizing this architecture is to use a series of concentric circles, similar to an onion. На рис. 5-7 показан пример такого стиля представления архитектуры.Figure 5-7 shows an example of this style of architectural representation.

Чистая архитектура (многослойное представление)

Рис. 5-7.Figure 5-7. Чистая архитектура (многослойное представление)Clean Architecture; onion view

На этой схеме зависимости направлены из самой внутренней окружности.In this diagram, dependencies flow toward the innermost circle. Ядро приложения называется так потому, что находится в самом центре этой схемы.The Application Core takes its name from its position at the core of this diagram. Как видно на схеме, ядро приложения не имеет зависимостей от других слоев приложения.And you can see on the diagram that the Application Core has no dependencies on other application layers. Сущности и интерфейсы приложения находятся в самом центре.The application's entities and interfaces are at the very center. Сразу после них, но все еще в пределах ядра приложения, расположены доменные службы, которые обычно реализуют интерфейсы, определенные во внутренней окружности.Just outside, but still in the Application Core, are domain services, which typically implement interfaces defined in the inner circle. За пределами ядра приложения располагаются слои пользовательского интерфейса и инфраструктуры, которые зависят от ядра приложения, но не друг от друга (обязательно).Outside of the Application Core, both the UI and the Infrastructure layers depend on the Application Core, but not on one another (necessarily).

На рис. 5-8 показана более привычная горизонтальная схема слоев, которая лучше отражает зависимости между слоем пользовательского интерфейса и другими слоями.Figure 5-8 shows a more traditional horizontal layer diagram that better reflects the dependency between the UI and other layers.

Чистая архитектура (горизонтальное представление слоев)

Рис. 5-8.Figure 5-8. Чистая архитектура (горизонтальное представление слоев)Clean Architecture; horizontal layer view

Обратите внимание, что сплошные стрелки соответствуют зависимостям времени компиляции, а пунктирные — зависимостям, которые существуют только во время выполнения.Note that the solid arrows represent compile-time dependencies, while the dashed arrow represents a runtime-only dependency. В рамках чистой архитектуры слой пользовательского интерфейса работает с интерфейсами, которые определены в ядре приложения во время компиляции, и в идеальном случае не должен знать ничего о типах реализации, определенных в слое инфраструктуры.With the clean architecture, the UI layer works with interfaces defined in the Application Core at compile time, and ideally shouldn't know about the implementation types defined in the Infrastructure layer. Тем не менее во время выполнения эти типы реализации необходимы для выполнения приложения, поэтому они должны существовать и быть привязаны к интерфейсам ядра приложения посредством внедрения зависимостей.At run time, however, these implementation types are required for the app to execute, so they need to be present and wired up to the Application Core interfaces via dependency injection.

На рис. 5-9 показано более подробное представление архитектуры приложения ASP.NET Core, построенного с соблюдением этих рекомендаций.Figure 5-9 shows a more detailed view of an ASP.NET Core application's architecture when built following these recommendations.

Схема чистой архитектуры ASP.NET Core

Рис. 5-9.Figure 5-9. Схема чистой архитектуры ASP.NET Core.ASP.NET Core architecture diagram following Clean Architecture.

Поскольку ядро приложения не зависит от инфраструктуры, для этого слоя легко писать автоматические модульные тесты.Because the Application Core doesn't depend on Infrastructure, it's very easy to write automated unit tests for this layer. На рис. 5-10 и 5-11 показано, как эти тесты вписываются в такую архитектуру.Figures 5-10 and 5-11 show how tests fit into this architecture.

Модульное тестирование ядра

Рис. 5-10.Figure 5-10. Изолированное модульное тестирование ядра приложения.Unit testing Application Core in isolation.

Интеграционное тестирование

Рис. 5-11.Figure 5-11. Интеграционное тестирование реализаций инфраструктуры с внешними зависимостями.Integration testing Infrastructure implementations with external dependencies.

Поскольку слой пользовательского интерфейса не имеет прямых зависимостей от типов, определенных в проекте инфраструктуры, будет так же просто менять реализации в целях тестирования или в случае изменения требований к приложению.Since the UI layer doesn't have any direct dependency on types defined in the Infrastructure project, it's likewise very easy to swap out implementations, either to facilitate testing or in response to changing application requirements. ASP.NET Core предлагает встроенную поддержку внедрения зависимостей, в связи с чем такая архитектура представляет собой оптимальный подход к структурированию нетривиальных монолитных приложений.ASP.NET Core's built-in use of and support for dependency injection makes this architecture the most appropriate way to structure non-trivial monolithic applications.

Для монолитных приложений проекты ядра приложения, инфраструктуры и пользовательского интерфейса выполняются как единое приложение.For monolithic applications, the Application Core, Infrastructure, and UI projects are all run as a single application. Во время выполнения архитектура приложения будет выглядеть так, как показано на рис. 5-12.The runtime application architecture might look something like Figure 5-12.

Архитектура ASP.NET Core 2

Рис. 5-12.Figure 5-12. Пример архитектуры приложения ASP.NET Core во время выполнения.A sample ASP.NET Core app's runtime architecture.

Упорядочение кода в рамках чистой архитектурыOrganizing code in Clean Architecture

В решении с чистой архитектурой для каждого проекта четко определены обязанности.In a Clean Architecture solution, each project has clear responsibilities. Фактически, каждому проекту будут принадлежать определенные типы, а в проектах будут представлены соответствующие этим типам папки.As such, certain types belong in each project and you'll frequently find folders corresponding to these types in the appropriate project.

Ядро приложенияApplication Core

Ядро приложения содержит бизнес-модель, которая включает в себя сущности, службы и интерфейсы.The Application Core holds the business model, which includes entities, services, and interfaces. Такие интерфейсы включают абстракции для операций, которые будут выполняться с использованием архитектуры, включая операции доступа к данным или файловой системе, сетевые вызовы и т. д. В некоторых случаях службы или интерфейсы, определенные в этом слое, должны работать с типами, не являющимися типами сущностей, которые не имеют зависимостей от пользовательского интерфейса или инфраструктуры.These interfaces include abstractions for operations that will be performed using Infrastructure, such as data access, file system access, network calls, etc. Sometimes services or interfaces defined at this layer will need to work with non-entity types that have no dependencies on UI or Infrastructure. Они могут определяться как простые объекты передачи данных.These can be defined as simple Data Transfer Objects (DTOs).

Типы ядра приложенияApplication Core types
  • Сущности (сохраняемые классы бизнес-модели)Entities (business model classes that are persisted)
  • интерфейсов,Interfaces
  • СлужбыServices
  • Объекты передачи данныхDTOs

ИнфраструктураInfrastructure

Как правило, проект инфраструктуры включает реализацию доступа к данным.The Infrastructure project typically includes data access implementations. В типовом веб-приложении ASP.NET Core эта реализация включает Entity Framework (EF) DbContext, любые определенные объекты Migration EF Core, а также классы реализации доступа к данным.In a typical ASP.NET Core web application, these implementations include the Entity Framework (EF) DbContext, any EF Core Migration objects that have been defined, and data access implementation classes. Наиболее распространенный подход к абстрагированию кода реализации доступа к данным заключается в использовании конструктивного шаблона репозитория.The most common way to abstract data access implementation code is through the use of the Repository design pattern.

Помимо реализации доступа к данным, проект инфраструктуры должен также включать реализации служб, которые должны взаимодействовать с инфраструктурными задачами.In addition to data access implementations, the Infrastructure project should contain implementations of services that must interact with infrastructure concerns. Эти службы должны реализовывать интерфейсы, определенные в ядре приложения. Таким образом, инфраструктура должна содержать ссылку на проект ядра приложения.These services should implement interfaces defined in the Application Core, and so Infrastructure should have a reference to the Application Core project.

Типы инфраструктурыInfrastructure types
  • Типы EF Core (DbContext, Migration)EF Core types (DbContext, Migration)
  • Типы реализации доступа к данным (репозитории)Data access implementation types (Repositories)
  • Службы, связанные с инфраструктурой (например, FileLogger или SmtpNotifier)Infrastructure-specific services (for example, FileLogger or SmtpNotifier)

Уровень пользовательского интерфейсаUI Layer

Слой пользовательского интерфейса в приложении MVC ASP.NET Core выступает в качестве точки входа для приложения.The user interface layer in an ASP.NET Core MVC application is the entry point for the application. Этот проект должен ссылаться на слой ядра приложения, а его типы должны взаимодействовать с инфраструктурой строго через интерфейсы, определенные в ядре приложения.This project should reference the Application Core project, and its types should interact with infrastructure strictly through interfaces defined in Application Core. В слое пользовательского интерфейса не должны разрешаться прямое создание экземпляров для типов слоя инфраструктуры, а также их статические вызовы.No direct instantiation of or static calls to the Infrastructure layer types should be allowed in the UI layer.

Типы слоев пользовательского интерфейсаUI Layer types
  • КонтроллерыControllers
  • ФильтрыFilters
  • ПредставленияViews
  • Модели представленийViewModels
  • ЗапускStartup

Класс Startup отвечает за настройку приложений и запись типов реализации в интерфейсы, обеспечивая корректную работу внедрения зависимостей во время выполнения.The Startup class is responsible for configuring the application, and for wiring up implementation types to interfaces, allowing dependency injection to work properly at run time.

Примечание

Чтобы привязать внедрение зависимостей в ConfigureServices в файле Startup.cs проекта пользовательского интерфейса, в этом проекте может потребоваться ссылка на проект инфраструктуры.In order to wire up dependency injection in ConfigureServices in the Startup.cs file of the UI project, the project may need to reference the Infrastructure project. Эту зависимость можно исключить. Проще всего это сделать с помощью настраиваемого контейнера внедрения зависимостей.This dependency can be eliminated, most easily by using a custom DI container. В рамках этого примера применяется простейший подход, при котором разрешаются ссылки из проекта пользовательского интерфейса на проект инфраструктуры.For the purposes of this sample, the simplest approach is to allow the UI project to reference the Infrastructure project.

Монолитные приложения и контейнерыMonolithic applications and containers

Вы можете создать одно монолитное веб-приложение или службу и развернуть их как контейнер.You can build a single and monolithic-deployment based Web Application or Service and deploy it as a container. В рамках приложения монолитность может не соблюдаться, однако будет реализована организация на основе нескольких библиотек, компонентов или слоев.Within the application, it might not be monolithic but organized into several libraries, components, or layers. Внешне оно будет представлять собой единый контейнер — единый процесс, единое веб-приложение или единую службу.Externally, it's a single container like a single process, single web application, or single service.

Для управления этой моделью вы развертываете один контейнер, представляющий собой приложение.To manage this model, you deploy a single container to represent the application. Для увеличения масштаба просто добавьте дополнительные копии с подсистемой балансировки нагрузки спереди.To scale, just add additional copies with a load balancer in front. Управлять одним развертыванием в одном контейнере или виртуальной машине гораздо проще.The simplicity comes from managing a single deployment in a single container or VM.

Рис. 5-13.

Вы можете включить в один контейнер несколько компонентов, библиотек или внутренних слоев, как показано на рис. 5-13.You can include multiple components/libraries or internal layers within each container, as illustrated in Figure 5-13. Такой монолитный шаблон может конфликтовать с принципом контейнера: "контейнер выполняет одно дело и в одном процессе".But, following the container principle of "a container does one thing, and does it in one process", the monolithic pattern might be a conflict.

Недостаток этого подхода становится очевидным, когда приложение разрастается и его необходимо масштабировать.The downside of this approach comes if/when the application grows, requiring it to scale. Если масштабируется приложение целиком, все получится.If the entire application scales, it's not really a problem. Но в большинстве случаев необходимо масштабировать всего несколько частей приложения, пока другие компоненты работают нормально.However, in most cases, a few parts of the application are the choke points requiring scaling, while other components are used less.

В примере приложения для электронной торговли, вероятнее всего, потребуется масштабирование компонента со сведениями о товарах.Using the typical eCommerce example, what you likely need to scale is the product information component. Клиенты чаще просматривают товары, чем приобретают их.Many more customers browse products than purchase them. Клиенты чаще складывают товары в корзину, чем оплачивают их.More customers use their basket than use the payment pipeline. Не так много клиентов пишут комментарии или просматривают историю покупок.Fewer customers add comments or view their purchase history. И у вас скорее всего может быть лишь несколько сотрудников в одном регионе, которые управляют содержимым и маркетинговыми кампаниями.And you likely only have a handful of employees, in a single region, that need to manage the content and marketing campaigns. При масштабировании монолитных решений весь код развертывается многократно.By scaling the monolithic design, all the code is deployed multiple times.

Помимо того, что необходимо масштабировать все компоненты, изменения в одном компоненте требуют полного повторного тестирования всего приложения и полного повторного развертывания всех его экземпляров.In addition to the "scale everything" problem, changes to a single component require complete retesting of the entire application, and a complete redeployment of all the instances.

Монолитный подход нашел широкое распространение и используется многими организациями при разработке архитектуры.The monolithic approach is common, and many organizations are developing with this architectural approach. Во многих случаях это позволяет добиться желаемых результатов, однако иногда организация сталкивается с достигнутыми ограничениями.Many are having good enough results, while others are hitting limits. Во многих организациях приложения строились по такой модели, поскольку несколько лет назад с помощью существующих инструментов и инфраструктуры слишком сложно было создавать архитектуры, ориентированные на службы (SOA), и проблем не возникало, пока приложение не начинало разрастаться.Many designed their applications in this model, because the tools and infrastructure were too difficult to build service-oriented architectures (SOA), and they didn't see the need until the app grew. Если ваша организация столкнулась с ограничениями монолитного подхода, следующим логичным шагом может стать разбиение приложения для более эффективного использования контейнеров и микрослужб.If you find you're hitting the limits of the monolithic approach, breaking up the app to enable it to better leverage containers and microservices may be the next logical step.

Рис. 5-14

Монолитные приложения в Microsoft Azure можно развертывать с использованием выделенных виртуальных машин для каждого экземпляра.Deploying monolithic applications in Microsoft Azure can be achieved using dedicated VMs for each instance. С помощью масштабируемых наборов виртуальных машин Azure можно легко масштабировать виртуальные машины.Using Azure Virtual Machine Scale Sets, you can easily scale the VMs. Службы приложений Azure также могут выполнять монолитные приложения и легко масштабировать экземпляры, и вам не придется управлять виртуальными машинами.Azure App Services can run monolithic applications and easily scale instances without having to manage the VMs. Службы приложений Azure также могут выполнять отдельные экземпляры контейнеров Docker, упрощая развертывание.Azure App Services can run single instances of Docker containers as well, simplifying the deployment. С помощью Docker вы можете развернуть одну виртуальную машину на узле Docker и выполнять на ней несколько экземпляров.Using Docker, you can deploy a single VM as a Docker host, and run multiple instances. Для управления масштабированием можно использовать систему балансировки Azure, как показано на рис. 5-14.Using the Azure balancer, as shown in the Figure 5-14, you can manage scaling.

Развертыванием на различных узлах можно управлять с помощью традиционных методов развертывания.The deployment to the various hosts can be managed with traditional deployment techniques. Узлами Docker можно управлять с помощью вводимых вручную команд вида docker run или автоматизированно, например с помощью конвейеров непрерывной поставки (CD).The Docker hosts can be managed with commands like docker run performed manually, or through automation such as Continuous Delivery (CD) pipelines.

Развертывание монолитного приложения в контейнереMonolithic application deployed as a container

Использование контейнеров для управления развертываниями монолитных приложений имеет свои преимущества.There are benefits of using containers to manage monolithic application deployments. Масштабировать экземпляры контейнера гораздо быстрее и проще, чем развертывать дополнительные виртуальные машины.Scaling the instances of containers is far faster and easier than deploying additional VMs. Даже при использовании масштабируемых наборов виртуальных машин применяется основанный на экземплярах подход.Even when using virtual machine scale sets to scale VMs, they take time to instance. При развертывании в виде экземпляров приложения управление конфигурацией приложения осуществляется в составе виртуальной машины.When deployed as app instances, the configuration of the app is managed as part of the VM.

Развертывание обновлений в виде образов Docker выполняется гораздо быстрее и эффективнее с точки зрения использования сети.Deploying updates as Docker images is far faster and network efficient. Образы Docker обычно запускаются за считанные секунды, что позволяет ускорить выпуск.Docker Images typically start in seconds, speeding rollouts. Остановить образ Docker можно с помощью команды docker stop, и обычно это происходит моментально.Tearing down a Docker instance is as easy as issuing a docker stop command, typically completing in less than a second.

Так как контейнеры по своей природе являются неизменяемыми, вам не придется беспокоиться о возможности повреждения виртуальной машины, когда скрипты обновления не учитывают некоторые оставшиеся на диске конфигурации или файлы.As containers are inherently immutable by design, you never need to worry about corrupted VMs, whereas update scripts might forget to account for some specific configuration or file left on the disk.

Контейнеры Docker можно использовать для монолитного развертывания простых веб-приложений.You can use Docker containers for a monolithic deployment of simpler web applications. Это позволяет оптимизировать конвейеры непрерывной интеграции и непрерывного развертывания, а также добиться успешного развертывания в рабочей среде.This approach improves continuous integration and continuous deployment pipelines and helps achieve deployment-to-production success. Вам больше не придется гадать, почему решение, выполняющееся на вашем компьютере, не выполняется в рабочей среде.No more “It works on my machine, why does it not work in production?”

Архитектура на основе микрослужб имеет много преимуществ, но обратной стороной является повышение сложности.A microservices-based architecture has many benefits, but those benefits come at a cost of increased complexity. В некоторых случаях затраты перевешивают преимущества, и лучше прибегнуть к монолитному развертыванию приложения в одном контейнере или буквально нескольких.In some cases, the costs outweigh the benefits, so a monolithic deployment application running in a single container or in just a few containers is a better option.

Монолитное приложение может быть непросто разделить на отдельные микрослужбы.A monolithic application might not be easily decomposable into well-separated microservices. Микрослужбы должны работать независимо друг от друга для повышения отказоустойчивости приложения.Microservices should work independently of each other to provide a more resilient application. Если приложение невозможно разложить на независимые функциональные составляющие, то его разделение лишь увеличит сложность.If you can't deliver independent feature slices of the application, separating it only adds complexity.

Приложению пока может не требоваться независимое масштабирование компонентов.An application might not yet need to scale features independently. Многие приложения, когда им требуется использовать более одного экземпляра, относительно легко клонируют весь экземпляр.Many applications, when they need to scale beyond a single instance, can do so through the relatively simple process of cloning that entire instance. Дополнительный труд по разделению приложений на отдельные службы предоставляет минимум преимуществ, тогда как масштабирование полноценных экземпляров приложения — это простое и экономичное решение.The additional work to separate the application into discrete services provides a minimal benefit when scaling full instances of the application is simple and cost-effective.

На ранних этапах развертывания приложения может отсутствовать ясное представление о том, где пролегают границы между функциональными областями.Early in the development of an application, you might not have a clear idea where the natural functional boundaries are. В процессе разработки продукта, обладающего минимальным необходимым набором возможностей, его естественное разделение на части может быть не очевидным.As you develop a minimum viable product, the natural separation might not yet have emerged. Некоторые из этих условий могут быть временными.Some of these conditions might be temporary. Вы можете сначала создать монолитное приложение, а в дальнейшем отделить некоторые компоненты для разработки и развертывания в качестве микрослужб.You might start by creating a monolithic application, and later separate some features to be developed and deployed as microservices. Другие условия могут быть неотъемлемыми особенностями приложения. Это означает, что приложение в принципе невозможно разделить на несколько микрослужб.Other conditions might be essential to the application's problem space, meaning that the application might never be broken into multiple microservices.

Разделение приложения на множество отдельных процессов также приводит к накладным расходам.Separating an application into many discrete processes also introduces overhead. При разделении компонента на несколько процессов повышается сложность.There's more complexity in separating features into different processes. Усложняются протоколы обмена данными.The communication protocols become more complex. Вместо вызовов методов необходимо использовать асинхронное взаимодействие между службами.Instead of method calls, you must use asynchronous communications between services. При переходе на архитектуру микрослужб необходимо добавить множество стандартных блоков, реализованных в версии приложения eShopOnContainers на основе микрослужб: обработку шины событий, отказоустойчивость и повторную отправку сообщений, итоговую согласованность и многое другое.As you move to a microservices architecture, you need to add many of the building blocks implemented in the microservices version of the eShopOnContainers application: event bus handling, message resiliency and retries, eventual consistency, and more.

Гораздо более простой пример приложения eShopOnWeb поддерживает использование одного монолитного контейнера.The much simpler eShopOnWeb reference application supports single-container monolithic container usage. Приложение включает одно веб-приложение с традиционными представлениями MVC, веб-API и Razor Pages.The application includes one web application that includes traditional MVC views, web APIs, and Razor Pages. Это приложение может запускаться из корня решения с помощью команд docker-compose build и docker-compose up.This application can be launched from the solution root using the docker-compose build and docker-compose up commands. Эта команда настраивает контейнер для веб-экземпляра с помощью Dockerfile из корневого каталога веб-проекта и выполняет контейнер в указанном порте.This command configures a container for the web instance, using the Dockerfile found in the web project's root, and runs the container on a specified port. Вы можете скачать исходный код этого приложения из GitHub и запустить его в локальной системе.You can download the source for this application from GitHub and run it locally. Даже такое монолитное приложение выигрывает от развертывания в контейнерной среде.Even this monolithic application benefits from being deployed in a container environment.

Во-первых, контейнерное развертывание означает, что каждый экземпляр приложения выполняется в одной и той же среде.For one, the containerized deployment means that every instance of the application runs in the same environment. Это относится и к среде разработки, в которой проводятся начальные этапы тестирования и разработки.This approach includes the developer environment where early testing and development take place. Команда разработчиков может запускать приложение в контейнерной среде, которая аналогична рабочей.The development team can run the application in a containerized environment that matches the production environment.

Кроме того, контейнерные приложения обеспечивают более экономичное горизонтальное масштабирование.In addition, containerized applications scale out at a lower cost. Контейнерная среда позволяет эффективнее организовывать совместное использование ресурсов, чем традиционные среды виртуальных машин.Using a container environment enables greater resource sharing than traditional VM environments.

Наконец, помещая приложение в контейнеры, вы разделяете бизнес-логику и сервер хранилища.Finally, containerizing the application forces a separation between the business logic and the storage server. По мере масштабирования приложения все контейнеры будут использовать один физический носитель данных.As the application scales out, the multiple containers will all rely on a single physical storage medium. В качестве хранилища, как правило, используется сервер высокой доступности с базой данных SQL Server.This storage medium would typically be a high-availability server running a SQL Server database.

Поддержка DockerDocker support

Проект eShopOnWeb работает в .NET.The eShopOnWeb project runs on .NET. Поэтому его можно запускать как в контейнерах Linux, так и в контейнерах Windows.Therefore, it can run in either Linux-based or Windows-based containers. Обратите внимание на то, что для развертывания Docker необходимо использовать тот же тип узла для SQL Server.Note that for Docker deployment, you want to use the same host type for SQL Server. Контейнеры на основе Linux требуют меньше ресурсов и более предпочтительны.Linux-based containers allow a smaller footprint and are preferred.

Вы можете использовать Visual Studio 2017, чтобы добавить поддержку Docker в существующее приложение, щелкнув проект в обозревателе решений правой кнопкой мыши и выбрав Добавить > Поддержка Docker.You can use Visual Studio 2017 or later to add Docker support to an existing application by right-clicking on a project in Solution Explorer and choosing Add > Docker Support. Таким образом вы добавите необходимые файлы и внесете изменения в проект для их использования.This step adds the files required and modifies the project to use them. В текущем примере eShopOnWeb эти файлы уже есть.The current eShopOnWeb sample already has these files in place.

Файл docker-compose.yml на уровне решения содержит сведения о том, какие образы необходимо создать и какие контейнеры запустить.The solution-level docker-compose.yml file contains information about what images to build and what containers to launch. Этот файл позволяет использовать команду docker-compose для запуска нескольких приложений одновременно.The file allows you to use the docker-compose command to launch multiple applications at the same time. В этом случае он запускает только веб-проект.In this case, it is only launching the Web project. Вы также можете с его помощью настроить зависимости, например отдельный контейнер базы данных.You can also use it to configure dependencies, such as a separate database container.

version: '3'

services:
  eshopwebmvc:
    image: eshopwebmvc
    build:
      context: .
      dockerfile: src/Web/Dockerfile
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    ports:
      - "5106:5106"

networks:
  default:
    external:
      name: nat

Файл docker-compose.yml ссылается на Dockerfile в проекте Web.The docker-compose.yml file references the Dockerfile in the Web project. С помощью Dockerfile можно указать, какой базовый контейнер будет использоваться и как приложение будет настроено на нем.The Dockerfile is used to specify which base container will be used and how the application will be configured on it. Dockerfile``Web:The Web' Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /app

COPY *.sln .
COPY . .
WORKDIR /app/src/Web
RUN dotnet restore

RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime
WORKDIR /app
COPY --from=build /app/src/Web/out ./

ENTRYPOINT ["dotnet", "Web.dll"]

Устранение неполадок с DockerTroubleshooting Docker problems

После запуска контейнерное приложение продолжает работать, пока его не остановят.Once you run the containerized application, it continues to run until you stop it. Используйте команду docker ps, чтобы посмотреть, какие контейнеры выполняются.You can view which containers are running with the docker ps command. Вы можете остановить выполняющийся контейнер с помощью команды docker stop и идентификатора контейнера.You can stop a running container by using the docker stop command and specifying the container ID.

Обратите внимание, что запущенные контейнеры Docker могут быть привязаны к портам, которые в противном случае вы могли бы использовать в среде разработки.Note that running Docker containers may be bound to ports you might otherwise try to use in your development environment. При попытке запустить или выполнить отладку приложения через порт, связанный с контейнером Docker, возникнет ошибка с сообщением о том, что сервер не может выполнить привязку к этому порту.If you try to run or debug an application using the same port as a running Docker container, you'll get an error stating that the server can't bind to that port. Проблемы будет решена, если вы остановите контейнер.Once again, stopping the container should resolve the issue.

Если вы хотите добавить поддержку Docker в приложение с помощью Visual Studio, убедитесь, что Docker Desktop при этом запущен.If you want to add Docker support to your application using Visual Studio, make sure Docker Desktop is running when you do so. Если при запуске мастера средство Docker Desktop не выполняется, мастер будет работать неправильно.The wizard won't run correctly if Docker Desktop isn't running when you start the wizard. Кроме того, мастер проверяет выбранные контейнеры, чтобы правильно реализовать поддержку Docker.In addition, the wizard examines your current container choice to add the correct Docker support. Чтобы добавить поддержку контейнеров Windows, необходимо запускать мастер при запущенном инструменте Docker Desktop с настроенными контейнерами Windows.If you want to add, support for Windows Containers, you need to run the wizard while you have Docker Desktop running with Windows Containers configured. Чтобы добавить поддержку контейнеров Linux, запускайте мастер при запущенном инструменте Docker с настроенными контейнерами Linux.If you want to add, support for Linux containers, run the wizard while you have Docker running with Linux containers configured.

Ссылки — общие архитектуры веб-приложенийReferences – Common web architectures