常用 Web 应用程序体系结构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 应用域中运行的可执行文件或单个 Web 应用程序。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 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.

一个新的 ASP.NET Core 项目,不管是在 Visual Studio 中还是通过命令行创建,最初都是简单的“一体式”整体应用程序。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.

图 5-1Figure 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. 用户界面 (UI) 问题(模型、视图和控制器)驻留于多个文件夹中,这些文件夹未按字母顺序组合在一起。User interface (UI) concerns (models, views, controllers) reside in multiple folders, which aren't grouped together alphabetically. 将其他 UI 级别的构造(例如筛选器或 ModelBinder)添加到它们自己的文件夹时,问题只会变得更糟。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 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. 这种重复使用很有用,因为它意味着需要编写的代码变少,还因为它可以让应用程序能够对某个实现进行标准化,从而遵循不要自我重复 (DRY) 原则。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 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 数据库来实现持久性,但稍后可能选择使用基于云的持久性策略,或 Web 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. 无需编写针对应用程序的真实数据层或 UI 层操作的测试,可在测试时使用提供请求的已知响应的假实现来替换这些层。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 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-2Figure 5-2. 典型的应用程序层次。Typical application layers.

这些层经常简称为 UI、BLL(业务逻辑层)和 DAL(数据访问层)。These layers are frequently abbreviated as UI, BLL (Business Logic Layer), and DAL (Data Access Layer). 使用此体系结构,用户可通过 UI 层(仅与 BLL 交互)提出请求。Using this architecture, users make requests through the UI layer, which interacts only with the BLL. 反过来,BLL 可为数据访问请求调用 DAL。The BLL, in turn, can call the DAL for data access requests. UI 层不应直接向 DAL 提出任何请求,也不得通过其他途径直接与持久性发生交互。The UI layer shouldn't make any requests to the DAL directly, nor should it interact with persistence directly through other means. 同样,BLL 应仅通过 DAL 与持久性发生交互。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. 即,UI 层依赖于 BLL,而 BLL 依赖于 DAL。That is, the UI layer depends on the BLL, which depends on the DAL. 这意味着,通常保存应用程序中最重要的逻辑的 BLL层,必须依赖于数据访问的实现方式(且通常依赖于数据库的存在)。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-3Figure 5-3. 具有三个项目的简单整体式应用程序。A simple monolithic application with three projects.

尽管出于组织架构目的,此应用程序使用多个项目,但它仍作为单一单位进行部署,且其客户端以单一 Web 应用的形式与其交互。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.

图 5-4Figure 5-4. Azure Web 应用简单部署Simple 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.

图 5-5Figure 5-5. 将 Web 应用部署到 Azure 应用服务Deploying 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. 纵向扩展指的是向承载应用的服务器添加额外的 CPU、内存、磁盘空间或其他资源。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 中缩放 Web 应用程序最简单的方法是在应用程序的应用服务计划中手动配置缩放。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.

如 5-6Figure 5-6. Azure 中的应用服务计划缩放。App Service Plan scaling in Azure.

干净体系结构Clean architecture

遵循依赖倒置原则以及域驱动设计原则 (DDD) 的应用程序倾向于达到类似的体系结构。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.

备注

术语“干净体系结构”可应用于使用 DDD 原则生成以及未使用 DDD 原则生成的应用程序。The term Clean Architecture can be applied to applications that are built using DDD Principles as well as to those that are not built using DDD. 对于前者,这种组合可称为“干净 DDD 体系结构”。In the case of the former, this combination may be referred to as "Clean DDD Architecture".

干净体系结构将业务逻辑和应用程序模型置于应用程序的中心。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 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-7Figure 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. 在应用程序内核外面,UI 和基础结构层均依赖于应用程序内核,但不一定彼此依赖。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 展示了可更好地反映 UI 和其他层之间的依赖关系的更传统的水平层次关系图。Figure 5-8 shows a more traditional horizontal layer diagram that better reflects the dependency between the UI and other layers.

图 5-8Figure 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. 使用干净体系结构,UI 层可使用编译时应用程序内核中定义的接口,理想情况下不应知道体系结构层中定义的实现类型。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.

ASPNET Core 体系结构

图 5-9Figure 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.

UnitTestCore

图 5-10Figure 5-10. 隔离状态下的单元测试应用程序内核。Unit testing Application Core in isolation.

IntegrationTests

图 5-11Figure 5-11. 使用外部依赖关系的集成测试基础结构实现。Integration testing Infrastructure implementations with external dependencies.

由于 UI 层对基础结构项目中定义的类型没有任何直接依赖关系,同样,可轻松交换实现,无论是为便于测试还是为应对不断变化的应用程序要求。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.

对于整体式应用程序,应用程序内核、基础结构和 UI 项目均作为单一应用程序运行。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-12Figure 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.

应用程序内核包含业务模型,后者包括实体、服务和接口。The Application Core holds the business model, which includes entities, services, and interfaces. 这些接口包括使用基础结构执行的操作(如数据访问、文件系统访问和网络调用等)的抽象。有时,在此层定义的服务或接口需要使用与 UI 或基础结构没有任何依赖关系的非实体类型。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. 这些类型可定义为简单的数据传输对象 (DTO)。These can be defined as simple Data Transfer Objects (DTOs).

应用程序内核类型Application Core types

  • 实体(保存的业务模型类)Entities (business model classes that are persisted)
  • 接口Interfaces
  • 服务Services
  • DTODTOs

基础结构项目通常包括数据访问实现。The Infrastructure project typically includes data access implementations. 在典型的 ASP.NET Core Web 应用程序中,这些实现包括 Entity Framework (EF) DbContext、任何已定义的 EF Core Migration 对象以及数据访问实现类。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 类型(DbContextMigrationEF Core types (DbContext, Migration)
  • 数据访问实现类型(存储库)Data access implementation types (Repositories)
  • 特定于基础结构的服务(如 FileLoggerSmtpNotifierInfrastructure-specific services (for example, FileLogger or SmtpNotifier)

ASP.NET Core MVC 应用程序中的用户界面层是应用程序的入口点。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. UI 层中不允许基础结构层类型的直接实例化(或静态调用)。No direct instantiation of or static calls to the Infrastructure layer types should be allowed in the UI layer.

UI 层类型UI layer types

  • ControllersControllers
  • 筛选器Filters
  • 视图Views
  • ViewModelsViewModels
  • 启动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.

备注

为在 UI 项目的 Startup.cs 文件的 ConfigureServices 中接通依赖关系注入,项目可能需要引用基础结构项目。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. 可通过使用自定义 DI 容器(最轻松的方式)消除此依赖关系。This dependency can be eliminated, most easily by using a custom DI container. 对于本示例,最简单的方式是允许 UI 项目引用基础结构项目。For the purposes of this sample, the simplest approach is to allow the UI project to reference the Infrastructure project.

整体式应用程序和容器Monolithic applications and containers

可以构建基于单个和整体部署的 Web 应用程序或服务,并将其部署为容器。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. 但在外部,它是单个容器,如单个进程、单个 Web 应用程序或单个服务。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. 为了简单起见,在单个容器或 VM 中管理单个部署。The simplicity comes from managing a single deployment in a single container or VM.

如图 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.

在 Microsoft Azure 中部署整体式应用程序可以通过使用每个实例的专用 VM 实现。Deploying monolithic applications in Microsoft Azure can be achieved using dedicated VMs for each instance. 使用 Azure 虚拟机规模集可轻松缩放 VM。Using Azure Virtual Machine Scale Sets, you can easily scale the VMs. Azure 应用服务 可运行整体式应用程序并轻松缩放实例,无需管理 VM。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,可将单个 VM 部署为 Docker 主机,并运行多个实例。Using Docker, you can deploy a single VM as a Docker host, and run multiple instances. 如图 5-14 所示,使用 Azure 均衡器可管理缩放。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 run 等命令可以手动管理 Docker 主机,也可以通过持续交付 (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. 缩放容器实例比部署额外的 VM 要快得多,也容易得多。Scaling the instances of containers is far faster and easier than deploying additional VMs. 即便使用虚拟机规模集缩放 VM,也需要时间才能到达实例。Even when using virtual machine scale sets to scale VMs, they take time to instance. 部署为应用实例时,应用的配置将作为 VM 的一部分进行管理。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.

正如容器从设计上来说,它的本质就是不可变的,因此你无需担心 VM 损坏,而更新脚本可能忘记考虑磁盘上剩下的某些特定配置或文件。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 disk.

对于简单 Web 应用程序的单片式部署,可以使用 Docker 容器。You can use Docker containers for monolithic deployment of simpler web applications. 这样可以改进持续集成和持续部署管道,并有助于成功实现部署到生产环境。This improves continuous integration and continuous deployment pipelines and helps achieve deployment-to-production success. 不再出现“为什么可以在我的计算机上正常运行,却不能在生产环境中正常运行?”的问题No more “It works in 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 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. 该应用程序包括一个 Web 应用程序,其中包括传统的 MVC 视图、Web API 和 Razor Pages。The application includes one web application that includes traditional MVC views, web APIs, and Razor Pages. 可使用 docker-compose builddocker-compose up 命令从解决方案根目录启动该应用程序。This application can be launched from the solution root using the docker-compose build and docker-compose up commands. 此命令使用 web 项目根目录中的 Dockerfile 为 Web 实例配置容器,并在指定端口上运行容器。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 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 lower cost. 使用容器环境比使用传统 VM 环境更有利于资源共享。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.

Docker 支持Docker support

eShopOnWeb 项目在 .NET Core 上运行。The eShopOnWeb project runs on .NET Core. 因此,该项目可以在基于 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 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. 在本示例中,它只启动 Web 项目。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 文件引用 Web 项目中的 DockerfileThe 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. Web' DockerfileThe Web' Dockerfile:

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 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/core/aspnet:2.2 AS runtime
WORKDIR /app
COPY --from=build /app/src/Web/out ./

# Optional: Set this here if not setting it from docker-compose.yml
# ENV ASPNETCORE_ENVIRONMENT Development

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

Docker 问题疑难解答Troubleshooting 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 命令并指定容器 ID 来停止正在运行的容器。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.

如果要使用 Visual Studio 向应用程序添加 Docker 支持,请确保执行此操作时 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 容器添加支持,需在配置了 Windows 容器的 Docker Desktop 运行时运行向导。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 容器添加支持,则运行向导,同时运行配置有 Linux 容器的 Docker。If you want to add support for Linux containers, run the wizard while you have Docker running with Linux containers configured.

参考 - 常见 Web 体系结构References – Common web architectures