C#8 和 .NET Core3 微服务实用手册(一)

通俗易懂,什么是.NET Core以及.NET Core能做什么 我们都知道.NET Core是一个可以用来构建现代、可伸缩和高性能的跨平台软件应用程序的通用开发框架。可用于为Windows、Linux和MacOS构建软件应用程序。与其他软件框架不同,.NET Core是最通用的框架,可用于构建各种软件,包括Web应用程序、移动应用程序、桌面应用程序、云服务、微服务、API、游戏和物联网应用程序。与其他框架不同,.NET Core并不局限于单一的编程语言,它支持C#、VB.NET、F#、XAML和TypeScript。这些编程语言都是开源的,由独立的社区管理。.NET C 阅读详情

原文:Hands-On Microservices with C# 8 and .NET Core 3

协议:CC BY-NC-SA 4.0

零、前言

这本书的目的是让你对现代软件开发中的微服务有一个广泛的了解,同时也用具体的例子来深入了解更多的细节.NET Core 应用接口。这本书涵盖了微服务的理论,然后是对用于开发虚拟应用的模式的高级理解,其中我们将涵盖解决方案中使用的概念,这些解决方案可以轻松配置为在云中或内部运行。

值得注意的是,微服务架构确实具有关键优势,尤其是在支持敏捷改进和复杂风险应用的传输方面。

然而,对于如何在微软生态系统中实现微服务,尤其是如何利用 Azure 和.NET Core 框架。这本书试图填补这一空白。

虽然微服务架构的许多方面不需要详细的解释,但我们试图总结这些概念,并提供微软文档链接供进一步阅读。在本书中,在我们开始开发我们想象中的应用之前,我们将涵盖开发基于微服务架构的应用所需的每一部分。

从一个简单的应用编程接口项目和整体应用开始,这本书描述了您可以执行的过渡到微服务的各种步骤。为了使代码示例更简单,本书使用了.NET Core 应用接口。最后一章包括一个应用,显示了整本书的关键。

这本书是给谁的

本书的目标读者包括希望在 Azure 云中找到设计高性能应用的一站式最佳实践的应用开发人员。这本书适用于 Azure 的所有开发者。它还面向希望了解和理解微服务体系结构并在其.NET Core 应用。这本书的编写/更新方式涵盖了从新手到高级用户。对于完全不熟悉微服务或者对这种体系结构方法只有理论上的了解,并且希望获得实用视角以便更好地管理其应用复杂性的开发人员来说,这是理想的选择。

这本书涵盖了什么

第 1 章、微服务简介,讨论了微服务的基础,回顾了一个整体应用,并揭示了它的局限性。您还将学习如何开始向微服务架构过渡。

第 2 章、重构整块,讨论应用的当前堆栈。它还包括的功能.NET Core 和 C#8,演示了如何实现独立的微服务,并研究了微服务之间的通信。

第三章、服务间的有效通信,涵盖了服务间的通信,包括同步和异步通信,以及 Azure 服务总线的概述。此外,本章借助于集成模式检查集成。在概述 Azure 服务结构之后,我们将了解 Docker 和容器,以及 Azure Kubernetes 服务、物联网集线器和物联网边缘的概述。

第 4 章用微软单元测试框架测试微服务,涵盖了各种类型的服务及其差异。在这里,您将使用微软单元测试框架、Moq 和 ASP.NET Core 应用编程接口实现测试方法。

第 5 章、使用 Docker 部署微服务,涵盖了部署范例并解释了部署术语。

第 6 章使用 Azure Active Directory 保护微服务揭示了通过使用 Azure Active Directory 部署示例应用来保护微服务的概念。

第 7 章、监控微服务,涵盖仪器仪表和遥测,其次是监控策略、日志记录和云中监控。

第 8 章、使用 Azure 扩展微服务,探讨可扩展性、基础架构扩展和微服务扩展。本章还概述了 Azure Redis 缓存。

第 9 章、反应式微服务介绍,借助代码示例为大家介绍反应式微服务。

第 10 章、设计模式和最佳实践,涵盖了帮助构建微服务的高级模式,以及聚合器、DDD、API 网关、共享数据微服务模式、反腐败层模式和 BFF。

第 11 章、构建微服务应用,研究了开发真实应用的各种方法。

第 12 章、微服务架构总结着眼于未来应用如何通过遵循微服务方法而发展。

附录,解释了 API 网关模式和后端到前端模式的优缺点,帮助我们了解它们的最佳实践。

充分利用这本书

本书假设您对 SOA、RESTful web 服务、API、服务器/客户端体系结构有一定的了解.NET Core、ASP.NET Core 和 C#。本书涵盖了高级主题、基本概念以及 Kubernetes 和 IoT Edge 等技术的概述。这本书的内容旨在帮助您开始开发基于微服务的应用。也可以作为综合指南。使用工具箱类比,这本书为现代应用开发人员提供了大量的工具,从低级代码设计发展到更高级的体系结构,以及当今基于微服务的应用开发中常用的重要概念、模式和最佳实践。

本书将涵盖以下要点:

  • 微服务的细节,包括整体与 SOA 和微服务架构的深入细节
  • 使用 C#8 和 ASP.NET Core 3 的示例
  • 当前应用堆栈和通过虚拟应用开发微服务的新堆栈的概述
  • 使用演示应用深入讨论设计模式和最佳实践

您需要安装带有最新更新的 Visual Studio 2019(最好是社区版)。所有代码示例都使用进行了测试。Windows 操作系统上的. NET Core 3.1。然而,它们也应该适用于未来的版本发布。

| 书中介绍的软件/硬件 | 操作系统要求 |
| ASP.NET Core 3.1 | Windows 操作系统 |
| C# 8 | Windows 操作系统 |
| SQL Server 2008R2 | Windows 操作系统 |
| SQL Server 2017 | Windows 操作系统 |

如果您正在使用本书的数字版本,我们建议您自己键入代码或通过 GitHub 存储库访问代码(下一节中提供了链接)。这样做将帮助您避免任何与复制/粘贴代码相关的潜在错误。

Few of the code example uses Angular 8  to showcase the UI part. There is no code for these component as these are only for UI and you would require to setup Angular 8 on your Windows OS.

下载示例代码文件

你可以从你在www.packt.com的账户下载这本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packt.com/support并注册将文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packt.com。
  2. 选择“支持”选项卡。
  3. 点击代码下载和勘误表。
  4. 在搜索框中输入图书的名称,并按照屏幕指示进行操作。

下载文件后,请确保使用最新版本的解压缩文件夹:

  • 视窗系统的 WinRAR/7-Zip
  • zipeg/izp/un ARX for MAC
  • 适用于 Linux 的 7-Zip/PeaZip

这本书的代码包也托管在 GitHub 上,网址为 https://GitHub . com/PacktPublishing/hand-On-microservice-with-CSharp-8-and-。网络核心 3 第三版。如果代码有更新,它将在现有的 GitHub 存储库中更新。

我们还有来自丰富的图书和视频目录的其他代码包,可在**【https://github.com/PacktPublishing/】**获得。看看他们!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。可以在这里下载:https://static . packt-cdn . com/downloads/9781789617948 _ color images . pdf

使用的约定

本书通篇使用了许多文本约定。

CodeInText:表示文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和推特句柄。下面是一个例子:“前面的代码用预先指定的值声明了两个readonly属性。”

代码块设置如下:

Range book = 1..4;
var res = Books[book] ;
Console.WriteLine($"\tElement of array using Range: Books[{book}] => {Books[book]}");

当我们希望将您的注意力吸引到代码块的特定部分时,相关的行或项目以粗体显示:

private static readonly int num1=5;
private static readonly int num2=6;

任何命令行输入或输出都编写如下:

dotnet --info

粗体:表示一个新的术语,一个重要的单词,或者你在屏幕上看到的单词。例如,菜单或对话框中的单词像这样出现在文本中。下面是一个示例:“从管理面板中选择系统信息。”

Warnings or important notes appear like this. Tips and tricks appear like this.

取得联系

我们随时欢迎读者的反馈。

一般反馈:如果你对这本书的任何方面有疑问,在你的信息主题中提到书名,发邮件给我们customercare@packtpub.com。

勘误表:虽然我们已经尽了最大的努力来保证内容的准确性,但是错误还是会发生。如果你在这本书里发现了一个错误,如果你能向我们报告,我们将不胜感激。请访问www.packt.com/submit-errata,选择您的图书,点击勘误表提交链接,并输入详细信息。

盗版:如果您在互联网上遇到任何形式的我们作品的非法拷贝,如果您能提供我们的位置地址或网站名称,我们将不胜感激。请通过copyright@packt.com联系我们,并提供材料链接。

如果你有兴趣成为一名作者:如果有一个你有专长的话题,你有兴趣写或者投稿一本书,请访问authors.packtpub.com。

复习

请留下评论。一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢?然后,潜在的读者可以看到并使用您不带偏见的意见来做出购买决定,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们的书的反馈。谢谢大家!

更多关于 Packt 的信息,请访问packt.com。

一、微服务简介

本章的重点是让您熟悉微服务。将您的应用分割成许多服务既不是面向服务架构的特征,也不是微服务的特征。然而,微服务结合了服务设计和来自 SOA 世界的最佳实践,以及一些新兴的实践,例如隔离部署、语义版本控制、提供轻量级服务以及多语种编程中的服务发现。我们实施微服务来满足业务功能,从而缩短上市时间并提高灵活性。

我们将在本章中讨论以下主题:

  • 微服务的起源
  • 讨论微服务
  • 了解微服务架构
  • 微服务的优势
  • SOA 与微服务
  • 理解整体建筑风格的问题
  • . NET 堆栈标准化的挑战
  • Azure 服务结构概述

在这一章中,我们将熟悉分层单片体系结构带来的问题。我们还将讨论在整体世界中这些问题的解决方案。在这一章的最后,我们将能够把一个单一的应用分解成一个微服务架构。

技术要求

本章包含各种代码示例来解释这些概念。代码保持简单,仅用于演示目的。

要运行和执行代码,您需要以下先决条件:

  • Visual Studio 2019
  • 。网络核心 3.1

要安装和运行这些代码示例,您需要安装 Visual Studio 2019(首选 IDE)。为此,请从安装说明中提到的下载链接下载 Visual Studio 2019(社区版,免费):https://docs . Microsoft . com/en-us/visualstudio/install/install-Visual Studio。Visual Studio 安装有多个版本。我们使用的是视窗操作系统。

如果你没有.NET Core 3.1 安装完毕,可以从以下链接下载:https://www.microsoft.com/net/download/windows。

The complete source code is available here: https://github.com/PacktPublishing/Hands-On-Microservices-with-CSharp-8-and-.NET-Core-3-Third-Edition/tree/master/Chapter%2001

微服务的起源

在讨论细节之前,我们应该探索微服务的起源或者任何新的框架、语言等等。微服务是一个流行词,我们应该意识到这种架构风格是如何发展到现在这个趋势的。有几个原因让你熟悉任何语言或框架的起源。要知道的最重要的事情如下:

  • 特定的语言或框架是如何进入语境的。
  • 谁是微服务新趋势架构风格的幕后推手?
  • 它是在什么地方建立的。

现在我们来讨论一下微服务的起源。术语微服务在 2011 年年中的一次软件架构师研讨会上首次被使用。2012 年 3 月,詹姆斯·刘易斯提出了他关于微服务的一些想法。到 2013 年底,来自 IT 行业的各种团体开始讨论微服务,到 2014 年,这个概念变得足够流行,被认为是大型企业的有力竞争者。

微服务没有官方定义。对这个术语的理解纯粹是基于用例和过去的讨论。

2014 年,詹姆斯·刘易斯和马丁·福勒走到一起,提供了一些真实世界的例子,并展示了微服务(参考http://martinfowler.com/microservices/)。

The development of a single application by combining small services (running on their processes) is called a microservice architectural style. Each service carries business capabilities and is independently deployed. Moreover, these services can be written in different languages and have different database storage.

微服务的微软官方文档页面(参考https://docs . Microsoft . com/en-us/azure/architecture/guide/architecture-style/微服务)对微服务架构风格的定义如下:

“Microservices architecture is a bunch of services, where each service is independently deployed and should implement a single business capability.”

看到刘易斯和福勒在这里定义的所有属性是非常重要的。他们将微服务定义为一种架构风格,开发人员可以利用这种风格来开发单个应用,业务逻辑分布在一堆小服务中,每个服务都有自己的持久存储功能。此外,请注意它的属性:它可以独立部署,可以在自己的进程中运行,是一种轻量级的通信机制,并且可以用不同的编程语言编写。

我们想强调这个具体的定义,因为它是整个概念的关键。随着我们的前进,当我们读完这本书时,所有的部分都会合在一起。目前,我们将详细研究微服务。

讨论微服务

我们已经讨论了微服务的一些定义;现在让我们详细讨论一下。

简而言之,微服务架构消除了 SOA 的大部分缺点。它也比 SOA 服务更面向代码(我们将在接下来的章节中详细讨论这一点)。

在我们继续理解架构之前,让我们讨论导致其存在的两个重要架构:

  • 整体建筑风格
  • 前进速度

我们大多数人都知道,当我们开发一个企业应用时,我们必须选择一种合适的架构风格。然后,在不同的阶段,初始模式被进一步改进和调整,以适应各种挑战,例如部署复杂性、庞大的代码库和可伸缩性问题。这正是单体架构风格如何演变成 SOA,然后导致微服务的原因。

探索整体架构

单体建筑风格是一种传统的建筑类型,在信息技术行业得到了广泛的应用。monolithic 一词并不新鲜,它是从 Unix 世界借用来的。在 Unix 中,大多数命令作为独立程序存在,其功能不依赖于任何其他程序。如下图所示,应用中可以有不同的组件,包括:

  • 用户界面:这处理所有的用户交互,同时用 HTML、JSON 或任何其他首选的数据交换格式(在网络服务的情况下)进行响应。
  • 业务逻辑:这包括应用于以用户输入、事件和数据库形式接收的输入的所有业务规则。
  • 数据库访问:这包含了为了查询和持久化对象而访问数据库的完整功能。一个被广泛接受的规则是,它是通过业务模块使用的,而不是直接通过面向用户的组件。

使用这种架构构建的软件是独立的。我们可以想象一个。包含各种组件的. NET 程序集,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/31858bcf-8cce-4bd4-920f-310bcfbf8c39.png

因为软件在这里是独立的,所以它的组件是相互关联和相互依赖的。即使其中一个模块中的简单代码更改也可能会破坏其他模块中的主要功能。这将导致我们需要测试整个应用的场景。由于业务严重依赖其企业应用框架,这一时间可能非常关键。

让所有组件紧密耦合会带来另一个挑战:每当我们执行或编译这样的软件时,所有组件都应该可用,否则构建将会失败。参考上图,该图代表单片架构,是独立的或单一的.NET 程序集项目。然而,单体架构也可能有多个程序集。这意味着,即使一个业务层(程序集、数据访问层程序集等)是分离的,但它们都将聚集在一起,在运行时作为一个进程运行。

用户界面依赖于其他组件的直接销售和库存,其方式类似于所有其他相互依赖的组件。在这种情况下,如果没有这些组件中的任何一个,我们将无法执行这个项目。升级它们的过程将会更加复杂,因为我们将不得不考虑其他需要修改代码的组件。这将导致比实际变更所需的更多的开发时间。

部署这样的应用将成为另一个挑战。在部署过程中,我们必须确保每个组件都被正确部署。如果我们不这样做,我们可能会在生产环境中面临许多问题。

如前所述,如果我们使用单体架构风格开发应用,我们可能会面临以下挑战:

  • 大代码库:这是一个代码行比注释多很多的场景。由于组件是互连的,我们将不得不处理重复的代码库。
  • 业务模块太多:这是针对同一系统内的模块。
  • 代码库复杂性:由于其他模块或服务需要修复,这导致代码被破坏的几率更高。
  • 复杂的代码部署:您可能会遇到需要整个系统部署的微小变化。
  • 影响整个系统的一个模块故障:这是针对相互依赖的模块。
  • 可扩展性:这是整个系统需要的,而不仅仅是其中的模块。
  • 模块间依赖:这是由于紧耦合。如果任何模块的操作需要,这将导致重大变化。

Tight coupling is a scenario in which one class is intended for many responsibilities, or in other words, when classes (mostly a group of classes) are dependent on each other.

  • 螺旋式上升的开发时间:这是由于代码的复杂性和相互依赖性。
  • 无法轻松适应新技术:在这种情况下,整个系统都需要升级。

如前所述,如果我们想要减少开发时间,简化部署,并提高企业应用软件的可维护性,我们应该避免传统或单片架构。因此,我们将着眼于 SOA。

面向服务的架构

在前一节中,我们讨论了单片架构及其局限性。我们还讨论了为什么它不适合我们的企业应用需求。为了克服这些问题,我们应该采取模块化的方法,将组件分开,使它们独立出来.NET 程序集。以上图中描述的方式使用一个服务或多个服务的系统被称为面向服务的体系结构 e ( SOA )。

The main difference between SOA and monolithic architecture is not one or multiple assemblies. As the service in SOA runs as a separate process, SOA scales better in comparison.

我们来讨论一下模块化架构,也就是 SOA。这是一种著名的架构风格,其中企业应用被设计为服务的集合。这些服务可能是 RESTful 或 ASMX 网络服务。为了更详细地理解 SOA,让我们首先讨论服务。

了解服务

在这种情况下,服务是 SOA 的一个基本概念。它们可以是一段代码、一个程序或向其他系统组件提供功能的软件。这段代码可以直接与数据库交互,也可以通过另一个服务间接交互。此外,它可以被客户端直接消费,其中客户端可以是网站、桌面应用、移动应用或任何其他设备应用。下图显示了各种客户端可以通过网络、桌面、移动或任何其他设备使用服务。服务可以在后端有或没有数据库支持:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/1b59d496-df97-42ce-8277-951223fa2e53.png

服务是指由其他系统(通常称为客户端 / 客户端应用)公开使用的一种功能。如前所述,这可以用一段代码、一个程序或软件来表示。这种服务通常通过 HTTP 传输协议公开。然而,HTTP 协议不是一个限制因素,可以选择一个被认为适合该场景的协议。

下图中,服务-直销直接与数据库交互,三个不同的客户端,即 Web 、桌面、移动正在消费服务。另一方面,我们有客户消费服务合作伙伴销售,这与服务渠道合作伙伴进行数据库访问。

产品销售服务是一组与客户端应用交互并直接或通过另一项服务提供数据库访问的服务,在本例中为服务–渠道合作伙伴。在服务-直销的案例中,如下图所示,它为网络商店、桌面应用和移动应用提供功能。该服务还与数据库进行交互,执行各种任务,即获取和保存数据。

通常,服务通过通信通道(通常是 HTTP 协议)与其他系统交互。这些服务可能部署在同一台服务器上,也可能不部署在单台服务器上:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/6eec98c4-20d3-441e-8006-de2fc2f6b93d.png

在上图中,我们已经计划了一个 SOA 示例场景。这里有很多细节需要注意,让我们开始吧。首先,我们的服务可以分布在不同的物理机器上。这里,服务直销托管在两台独立的机器上。可能不是整个业务功能,而是只有一部分驻留在服务器 1 上,其余部分驻留在服务器 2 上。类似地,服务合作伙伴销售似乎在服务器 3 和服务器 4 上有相同的安排。然而,这并没有阻止服务渠道合作伙伴在服务器 5 和服务器 6 上作为一个完整的集合进行托管。

我们将在以下几节中详细讨论 SOA。

让我们回忆一下单片架构。在这种情况下,我们没有使用它,因为它限制了代码的可重用性;它是一个独立的组件,所有组件都是相互连接和相互依赖的。对于部署,在这种情况下,在我们选择了 SOA 之后,我们将不得不部署我们的完整项目(参考前面的图表和后续的讨论)。现在,由于使用了这种架构风格,我们拥有代码可重用性和易于部署的优势。让我们根据前面的图表来检查这一点:

  • 复用性:多个客户端可以消费该服务。这也可以由其他服务同时消费。例如OrderService被 web 和移动客户端消费。OrderService现在也可以被报告仪表板用户界面使用。
  • 无状态:服务不会在客户端请求之间保持任何状态。这意味着服务不知道或不关心后续的请求来自已经/还没有发出前一个请求的客户端。
  • **基于契约:**接口使得任何服务在实现和消费两方面都是技术不可知的。它们还使它不受底层功能中代码更新的影响。
  • 可扩展性:一个系统可以向上扩展,SOA 可以通过适当的负载平衡单独集群。
  • 可升级性:推出新功能或引入现有功能的新版本非常容易。系统不会阻止您保留同一业务功能的多个版本。

本节讲述了 SOA,我们还讨论了服务的概念以及它们如何影响架构。接下来,我们将继续学习微服务架构的所有知识。

理解微服务架构

微服务架构是一种开发包含一组较小服务的单个应用的方法。这些服务相互独立,并在各自的流程中运行。这些服务的一个重要优势是它们可以独立开发和部署。换句话说,我们可以说微服务是隔离我们的服务的一种方式,这样它们就可以在设计、开发、部署和升级的环境中彼此完全独立地处理。

在单个应用中,我们有一个由用户界面、直销和库存组成的独立组件。在微服务体系结构中,应用的服务部分更改为以下描述:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/06735547-c1cb-457b-ab00-5bf6fb5d5259.png

在这里,业务组件被分离成单独的服务。这些独立的服务现在是更小的单元,早些时候存在于单体架构中的自包含组件中。直销和库存服务都是相互独立的,虚线描绘了它们在同一个生态系统中的存在,还没有被限制在一个范围内。

参考下图,描述用户与不同 API 的交互:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/66d8a9d5-9737-4673-bbc9-e8b6f9ec2973.png

从前面的图中,很明显我们的用户界面可以与任何服务交互。当用户界面调用任何服务时,不需要干预它。两个服务相互独立,不知道用户界面何时会调用另一个服务。这两个服务对自己的操作负责,而不对整个系统的任何其他部分负责。尽管我们更接近我们预期的微服务架构的布局。请注意,布局的先前表示并不完全是预期微服务架构的完整可视化。

In microservice architecture, services are small, independent units with their own persistent stores.

现在让我们应用这个最后的更改,这样每个服务都有自己的数据库来保存必要的数据。参考下图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/4d4d059d-8fe1-4cf2-be3a-132a4d8c1b7a.png

在这里,用户界面正在与服务交互,这些服务有自己独立的存储。在这种情况下,当用户界面调用直接销售服务时,直接销售的业务流程独立于库存服务中包含的任何数据或逻辑来执行。

使用微服务提供的解决方案有很多好处,包括:

  • 更小的代码库:每个服务都很小,因此作为一个单元更容易开发和部署。
  • 独立环境的易用性:随着服务的分离,所有开发人员独立工作,独立部署,没有人关心模块依赖。

随着微服务架构的采用,单片应用现在可以利用相关的优势,因为它们现在可以轻松扩展,并使用服务独立部署。

微服务中的消息传递

在处理微服务架构时,仔细考虑消息机制的选择是非常重要的。如果忽略这一方面,它可能会损害设计微服务架构的整个目的。在单片应用中,这不是问题,因为组件的业务功能是通过函数调用来调用的。另一方面,这是通过松散耦合的 web 服务级消息传递特性来实现的,其中服务主要基于 SOAP。在微服务消息机制的情况下,这应该是简单和轻量级的。

在微服务架构的各种框架或协议之间做出选择没有固定的规则。不过,这里有几点值得考虑。首先,它应该实现起来足够简单,不会给系统增加任何复杂性。其次,它应该非常轻量级,记住微服务架构可能严重依赖服务间消息传递这一事实。让我们继续考虑同步和异步消息传递的选择,以及不同的消息传递格式。

同步消息

同步消息传递是指系统期望从服务中得到及时的响应,系统等待直到从服务中收到响应。剩下的就是微服务最受欢迎的选择。这很简单,支持 HTTP 请求-响应,因此几乎没有空间去寻找替代方案。这也是大多数微服务实现使用 HTTP(基于 API 的样式)的原因之一。

异步消息传递

异步消息传递是指系统没有立即从服务中得到及时的响应,并且系统可以继续处理而不阻塞该调用。

让我们将这个消息传递概念融入到我们的应用中,看看它将如何改变我们的应用的工作和外观:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/f3b2f720-3656-4707-a5b7-8d3815f6cc7d.png

在上图中,当系统与销售数据库和/或库存数据库服务交互时,用户会得到响应,并将数据提取或推送到各自的数据库。用户(通过用户界面)对相应服务的呼叫不会阻止来自相同或不同用户的新呼叫。

消息格式

在过去的几年里,使用 MVC 之类的东西让我迷上了 JSON 格式。您也可以考虑 XML。使用应用编程接口风格的资源,这两种格式在 HTTP 上都可以。如果您需要使用二进制消息格式,也可以使用。我们不推荐任何特定的格式;您可以继续使用您喜欢的消息格式。

使用微服务

社区已经探索了大量的模式和架构,其中一些越来越受欢迎。每种解决方案都有各自的优缺点,因此对于公司来说,快速响应基本需求变得越来越重要,例如可扩展性、高性能和易于部署。任何一个方面如果不能以成本效益的方式实现,都很容易对大型企业产生负面影响,从而在盈利和非盈利企业之间产生巨大差异。

We will discuss scalability in detail in Chapter 8, Scaling Microservices with Azure.

在这种架构风格的帮助下,涉众可以确保他们的设计免受前面提到的问题的影响。同样重要的是要考虑这样一个事实,即在尊重所涉时间的同时,以具有成本效益的方式实现这一目标。

让我们看看微服务架构是如何工作的。

微服务架构的工作原理

微服务架构是一种架构风格,它将应用构建为松散耦合服务的集合。这些服务可以相互通信,也可以相互独立。基于微服务的应用的整体工作架构取决于用于开发应用的各种模式。例如,微服务可以基于后端或前端模式。我们将在第 10 章、设计模式和最佳实践中讨论各种模式。

到目前为止,我们已经讨论了微服务架构的各个方面,现在我们可以描述它是如何工作的;我们可以根据我们的设计方法使用任何组合,或者预测适合的模式。以下是使用微服务架构的一些好处:

  • 在当前的编程时代,每个人都应该遵循所有的 SOLID 原则。几乎所有的语言都是面向对象编程 ( OOP )。
  • 最好的方法是向其他或外部组件公开功能,允许任何其他编程语言使用该功能,而不遵守任何特定的用户界面(即,诸如 web 服务、API、REST 服务等服务)。
  • 整个系统按照一种不相互关联或不相互依赖的协作方式工作。
  • 每个组件都有自己的责任。换句话说,组件只负责一个功能。
  • 它用分离概念分离代码,分离的代码是可重用的。

微服务的优势

现在,让我们探索和讨论作为微服务相对于 SOA 和单体架构的优势的各种因素:

  • 扩展的性价比:你不需要投入很多就可以让整个应用可扩展。就购物车而言,我们可以简单地对产品搜索模块和订单处理模块进行负载平衡,同时省去不太常用的操作服务,如库存管理、订单取消和交货确认。
  • 清晰的代码边界:代码应该与组织的部门层级相匹配。在大型企业中,不同的部门赞助产品开发,这可能是一个巨大的优势。
  • 更容易的代码更改:代码的完成方式不依赖于其他模块的代码,只实现孤立的功能。如果做得好,那么一个微服务中的变化影响另一个微服务的机会是最小的。
  • 轻松部署:由于整个应用更像是一组相互隔离的生态系统,如果需要,可以一次部署一个微服务。其中任何一个的失败都不会使整个系统瘫痪。
  • 技术适配:你可以一夜之间将单个微服务或者一大堆微服务移植到不同的技术上,而你的用户甚至不知道。记得维护那些服务合同。
  • 分布式系统:这里隐含的意思,但需要提醒一句。确保异步调用使用良好,同步调用不会真正阻塞整个信息流。很好地使用数据分区。我们稍后会在本章的数据分区部分谈到这一点,所以现在不要担心。
  • 快速市场反应:世界竞争是一个确定的优势。如果您对新功能请求或系统内新技术的采用反应迟缓,用户往往会很快失去兴趣。

到目前为止,我们已经介绍了 SOA 和微服务架构。我们已经详细讨论了每一个。我们也看到了各自是如何独立的。在下一节中,我们将了解微服务和 SOA 之间的区别。

SOA 与微服务

如果你对微服务和 SOA 都没有完整的理解,你会混淆两者。从表面上看,微服务的特性和优势听起来几乎像是 SOA 的细长版本,许多专家认为,事实上,不需要像微服务这样的附加术语,SOA 可以实现微服务布局的所有属性。然而,事实并非如此。存在足够的差异,可以在技术上隔离它们。

SOA 的底层通信系统固有地存在以下问题:

  • 事实上,在 SOA 中开发的系统依赖于它的组件,组件之间是相互作用的。所以,不管你怎么努力,它最终都会在消息队列中面临瓶颈。
  • SOA 的另一个焦点是命令式组合。这样,我们就失去了使一个代码单元相对于面向对象程序可重用的途径。

我们都知道组织在基础设施上的支出越来越多。企业越大,开发的应用的所有权问题就越复杂。随着利益相关方数量的增加,满足他们所有不断变化的业务需求变得不可能。

In SOA, the development of services can be managed and organized within multiple teams. On the other hand, services can be developed, operated, and deployed independently when working with microservices. This helps to deploy new versions of services easily.

SOA uses an enterprise service bus (ESB) for communication; an ESB can be the reason for communication failures and can impact the entire application. This could happen in a scenario where one service is slowing down and communication is delayed, hampering the workings of the entire application. On the other hand, it would not be a problem in microservices; in the case of independent services, if one service is down, then only that microservice will be affected. In the case of interdependent services, if one of the services is down, then only a particular service(s) will be affected. The other microservices will continue to handle requests.

Data storage is common/sharable in the case of SOA. On the other hand, each service can have independent data storage in microservices.

这是微服务明显与众不同的地方。虽然云开发不在我们目前的讨论范围内,但说微服务架构的可扩展性、模块化和适应性可以通过使用云平台轻松扩展,这并不会损害我们。是时候改变了。

让我们看看微服务架构的先决条件。

微服务架构的先决条件

了解微服务架构实现所产生的生态系统非常重要。微服务的影响不仅仅是运营前的。任何选择微服务架构的组织的变化都是如此深刻,以至于如果他们没有做好准备来处理它们,用不了多久优势就会变成劣势。

在同意采用微服务架构后,明智的做法是具备以下先决条件:

  • 部署和 QA :需求会变得更加苛刻,从开发需求的转变会更快。这将要求您尽快部署和测试。如果只是少量的服务,那么这就不成问题了。然而,如果服务的数量不断增加,它可能会很快挑战现有的基础设施和实践。例如,您的质量保证和试运行环境可能不再足以测试开发团队返回的构建数量。
  • 开发运营团队的一个协作平台:随着应用走向公共领域,用不了多久,开发对抗 QA 的老套剧本又要上演了。这一次的不同之处在于,业务将处于危险之中。因此,您需要准备好以自动化方式快速响应,以便在需要时识别根本原因。
  • 监控框架:随着微服务数量的不断增加,您将很快需要一种方法来监控整个系统的功能和运行状况,以发现任何可能的瓶颈或问题。如果没有任何方法来监控已部署的微服务的状态以及由此产生的业务功能,任何团队都不可能采取主动部署方法。

本节解释了基于微服务架构的应用的先决条件。有了它们,下一节将帮助我们理解单片的问题.NET 基于堆栈的应用。

理解整体建筑风格的问题

在这一节中,我们将讨论单片集成电路的所有问题.NET 基于堆栈的应用。在单芯片应用中,核心问题是:扩展单芯片应用很困难。最终得到的应用拥有非常大的代码库,并在可维护性、部署和修改方面带来了挑战。在接下来的几节中,我们将了解扩展,然后我们将通过遵循扩展属性来应对部署挑战。

. NET 堆栈标准化的挑战

在单片应用技术中,堆栈依赖阻止了外部世界最新技术的引入。当前的堆栈带来了挑战,因为 web 服务本身也会受到这些挑战的困扰:

  • 安全性:由于对强认证方案没有明确共识,无法通过 web 服务识别用户。想象一下,一个银行应用发送包含用户凭据的未加密数据。所有提供免费无线网络的机场、咖啡馆和公共场所都很容易成为身份盗窃和其他网络犯罪的受害者。
  • 响应时间:虽然 web 服务本身在整体架构上提供了一定的灵活性,但是由于服务本身需要很长的处理时间,这种灵活性很快就会减少。因此,在这种情况下,web 服务没有任何问题。事实上,一个单一的应用需要大量的代码;复杂的逻辑使得 web 服务的响应时间很长,因此是不可接受的。
  • 吞吐率:这个偏高,结果阻碍了后续的操作。对于结帐操作来说,依靠调用库存 web 服务来搜索几百万条记录并不是一个坏主意。但是,当相同的库存服务为搜索整个门户的主要产品提供服务时,可能会导致业务损失。10 次呼叫中有一次服务呼叫失败将意味着业务的转化率降低 10%。
  • 频繁宕机:由于 web 服务是整个整体生态系统的一部分,因此每次升级或应用出现故障时,它们必然会停机且不可用。这意味着外部世界对应用 web 服务的任何 B2B 依赖的存在将进一步使决策复杂化,从而导致停机。这使得系统的较小升级看起来很昂贵;因此,这进一步增加了待定系统升级的积压。
  • 技术采用:为了采用或升级技术栈,需要对整个应用进行升级、测试和部署,因为模块是相互依赖的,项目的整个代码库都会受到影响。考虑支付网关模块使用需要合规性相关框架升级的组件。开发团队别无选择,只能升级框架本身,并仔细检查整个代码库,以抢先识别任何代码中断。当然,这仍然不排除生产崩溃,但这很容易让即使是最好的架构师和经理失眠。
  • 可用性:服务运行的时间百分比。
  • 响应时间:服务响应所花费的时间。
  • 吞吐量:处理请求的速率。

容错

单片应用具有很高的模块依赖性,因为它们紧密耦合。不同的模块以模块内的方式利用功能,由于其级联效应,即使单个模块故障也会导致系统停机。我们都知道,一个用户得不到产品搜索的结果,远没有整个系统崩溃那么严重。

使用 web 服务的解耦传统上是在架构级别尝试的。对于数据库级策略,ACID 已经被依赖了很长时间。让我们进一步研究这两点:

  • Web 服务:在目前的单体应用中,由于使用 Web 服务导致客户体验下降。即使客户尝试下订单,由于 web 服务响应时间长,甚至服务本身完全失败等原因,也可能导致无法成功下订单。甚至没有一次失败是可以接受的,因为用户倾向于记住他们最后的经历,并假设可能会重复。这不仅会导致可能的销售损失,还会失去未来的商业前景。Web 服务故障会导致依赖它们的系统出现级联故障。
  • ACID : ACID 是原子性、一致性、隔离性和持久性的缩写;这是数据库中的一个重要概念。它是存在的,但它是好事还是坏事要根据综合表现的总和来判断。它处理数据库级别的故障,毫无疑问,它确实提供了一些防止数据库错误蔓延的保障。同时,每个 ACID 操作都会妨碍/延迟其他组件/模块的操作。需要非常仔细地判断它造成的伤害大于益处的程度。

将要过渡到微服务的单一应用面临着与安全性、响应时间、可伸缩性相关的各种挑战,此外,它的模块高度依赖于彼此。当试图处理一个标准应用时,这些都是很大的挑战,尤其是一个单一的应用,它应该用于大量的用户。对于我们的单片应用来说,这里的主要和重要的一点是可伸缩性,这将在下一节中讨论。

系统的缩放特性

各种因素,如不同通信手段的可获得性、信息的便捷获取和开放的世界市场,正在导致企业快速增长,同时实现多样化。随着这种快速增长,越来越需要适应不断增长的客户群。扩展是任何企业在努力迎合不断增长的用户群时面临的最大挑战之一。

可扩展性描述系统/程序处理不断增加的工作负载的能力。换句话说,可伸缩性是指系统/程序的伸缩能力。

在开始下一部分之前,让我们详细讨论扩展,因为这将是我们练习的一个组成部分,因为我们正在努力从单块架构过渡到微服务。

有两种主要的可伸缩性策略或类型:

  1. 垂直缩放或放大
  2. 水平扩展或横向扩展

我们可以通过采用其中一种策略来扩展我们的应用。让我们进一步讨论这两种类型的扩展,看看如何扩展我们的应用。

垂直缩放或放大

在纵向扩展中,我们分析现有的应用,找出模块中由于执行时间较长而导致应用变慢的部分。提高代码的效率可能是一种策略,这样可以减少内存消耗。这个减少内存消耗的练习可以针对特定的模块或整个应用。另一方面,由于该策略涉及明显的挑战,我们可以向现有的信息技术基础架构添加更多资源,例如升级内存或添加更多磁盘驱动器,而不是更改应用。垂直缩放中的这两种路径都有其有益程度的限制。在一个特定的时间点之后,最终的收益将趋于平稳。请务必记住,这种扩展需要停机。

水平扩展或横向扩展

在水平扩展中,我们深入挖掘对整体性能有较大影响的模块,例如高并发性;这将使我们的应用能够为我们不断增长的用户群提供服务,用户群现在已经达到百万大关。我们还实现了负载平衡来处理更多的工作。向集群添加更多服务器的选项不需要停机,这是一个明显的优势。每个案例都有所不同,因此电力、许可证和冷却的额外成本是否值得,以及达到什么程度,将根据具体情况进行评估。

扩展将在第 8 章中详细介绍。

部署挑战

当前的应用也面临部署挑战。它被设计为一个整体应用,订单模块中的任何更改都需要重新部署整个应用。这很耗时,而且每次变更都必须重复整个周期,这意味着这可能是一个频繁的周期。在这种情况下,缩放只能是一个遥远的梦想。

正如关于扩展当前存在部署挑战的应用所讨论的那样,这些应用需要我们部署整个程序集,模块是相互依赖的,这是. NET 的单个程序集应用。一次性部署整个应用也使测试我们应用的整个功能成为强制性的。这种做法的影响将是巨大的:

  • 高风险部署:一次性部署整个解决方案或应用会带来高风险,因为所有模块都将被部署,即使其中一个模块发生了单次更改。
  • 更长的测试时间:由于我们必须部署完整的应用,我们将不得不测试整个应用的功能。没有测试我们不能直播。由于更高的相互依赖性,更改可能会导致其他模块出现问题。
  • 计划外停机:完整的生产部署需要代码进行全面测试,因此我们需要安排生产部署。这是一项耗时的任务,会导致长时间停机。虽然是计划停机,但在此期间,由于系统不可用,业务和客户都会受到影响;这可能会给企业造成收入损失。
  • 生产 bug:无 bug 部署将是任何项目经理的梦想。然而,这远非现实,每个团队都害怕这种错误部署的可能性。单片应用与这个场景没有什么不同,解决生产缺陷说起来容易做起来难。如果之前的错误没有解决,情况只会变得更加复杂。

组织一致性

在单片应用中,拥有庞大的代码库并不是您将面临的唯一挑战。拥有一个庞大的团队来处理这样的代码库是影响业务和应用增长的另一个问题。

要调整一个组织,最关心的因素是团队的目标。对于所有团队成员来说,团队目标应该相同,这一点非常重要:

  • 同一个目标:在一个团队中,所有的团队成员都有同一个目标,那就是每天结束时及时无 bug 的交付。然而,在当前的应用中有一个大的代码库意味着单一的架构风格对于团队成员来说不是一个舒适的领域。由于相互依赖的代码和相关的可交付成果,团队成员是相互依赖的,在代码中体验到的同样的效果也存在于开发团队中。在这里,每个人都在争先恐后地努力完成工作。互相帮助或尝试新事物的问题不会出现。简而言之,团队不是自组织的。

Roy Osherove defined three stages of a team as: survival phase—no time to learn, l****earning phase—learning to solve your own problems, and s****elf-organizing phase—facilitate, and experiment.

  • 不同的视角:开发团队由于功能增强、bug 修复或模块相互依赖等原因,在交付件上花费了太多的时间,阻碍了开发的简易性。质量保证团队依赖于开发团队,开发团队有自己的问题。一旦开发人员开始处理 bug、修复或特性增强,质量保证团队就会陷入困境。没有单独的环境或构建可供质量保证团队进行测试。这种延迟会阻碍整体交付,客户或最终用户将无法按时获得新功能或修复。

模块性

在我们的单片应用中,我们可能有一个Order模块,Order模块的变化会影响Stock模块,以此类推。正是模块化的缺失导致了这种情况。

这也意味着我们不能在另一个模块中重用一个模块的功能。代码没有被分解成可重用的结构化片段,以节省时间和精力。代码模块内没有隔离,因此没有通用代码可用。

业务在增长,客户在突飞猛进。来自不同地区的新客户或现有客户在使用该应用时有不同的偏好。有些人喜欢访问网站,但其他人更喜欢使用移动应用。该系统的结构意味着我们不能在一个网站和一个移动应用之间共享组件。这使得为企业推出移动/设备应用成为一项具有挑战性的任务。因此,由于客户更喜欢移动应用,企业损失惨重,业务受到影响。

困难在于替换使用第三方库的应用组件、支付网关等外部系统以及外部订单跟踪系统。在当前风格的整体架构应用中替换旧组件是一项乏味的工作。例如,如果我们考虑升级消耗外部订单跟踪系统的模块库,那么整个更改将会非常困难。此外,用另一个支付网关取代我们的支付网关将是一项复杂的任务。

在前面的任何场景中,每当我们升级组件时,我们都会升级应用中的所有内容,这需要对系统进行完整的测试,并且需要大量的停机时间。除此之外,升级可能会导致生产错误,这需要重复整个开发、测试和部署周期。

大数据库

我们当前的应用有一个庞大的数据库,包含一个带有大量索引的模式。就微调性能而言,这种结构是一项具有挑战性的工作:

  • 单一模式:数据库中的所有实体都集中在一个名为dbo的单一模式下。这再次阻碍了业务,因为对于属于不同模块的各种表的单一模式存在混淆。例如,客户表和供应商表属于同一个模式,即dbo。
  • 众多的存储过程:目前数据库中有大量的存储过程,其中也包含了相当大一部分的业务逻辑。有些计算是在存储过程中执行的。因此,当涉及到优化这些存储过程或将它们分解成更小的单元时,这些存储过程被证明是一项令人困惑的任务。

无论何时计划部署,团队都必须密切关注每个数据库更改。同样,这是一项耗时的工作,往往比构建和部署工作本身更加复杂。

大型数据库有其自身的局限性。在我们的整体应用中,我们有一个单一的模式数据库。这有很多存储过程和函数;所有这些都会影响数据库的性能。

在接下来的部分中,我们将讨论各种解决方案和其他方法来克服这些问题。但在此之前,我们需要先了解微服务的前提条件,然后再去挖掘这种架构风格。

微服务的先决条件

为了更好地理解微服务,让我们来看看 FlixOne Inc .的一个假想例子,以这个例子为基础,我们可以详细讨论所有的概念,看看准备好微服务是什么样子的。

FlixOne 是一家遍布印度的电子商务玩家。他们正以非常快的速度增长,同时实现业务多元化。他们已经建立了他们现有的系统.NET 框架,这是一个传统的三层架构。他们有一个庞大的数据库,是这个系统的核心,在他们的生态系统中还有外围应用。其中一个这样的应用是为他们的销售和物流团队准备的,它恰好是一个安卓应用。这些应用连接到其集中式数据中心,并面临性能问题。FlixOne 有一个由外部顾问支持的内部开发团队。参考下图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/a0752d9c-46e9-46f8-a3da-d263a6aa7c1e.png

上图描述了我们当前应用的广义,它是一个单一的.NET 程序集应用。在这里,我们有用于搜索和订购产品、跟踪订单和结账的用户界面。现在看下图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/c24e1891-0a7a-4229-bcb2-a5ef3ed6c7f7.png

上图仅描述了我们的购物 购物车模块。该应用是用 C#、MVC5 和实体框架构建的,它有一个单一的项目应用。这个图只是我们应用架构的图示概述。这个应用是基于网络的,可以从任何浏览器访问。最初,任何使用 HTTP 协议的请求都会到达使用 MVC5 和 jQuery 开发的用户界面。对于购物车活动,UI 与购物车模块交互,该模块是与数据库层交互的业务逻辑层(用 C#编写)。我们将数据存储在数据库中。

应用的功能概述

在这里,我们将了解 FlixOne 书店应用的功能概述。这只是为了可视化我们的应用。以下是应用的简化功能概述,显示了从主页到结账的过程:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/9d5a1851-df7c-4acf-ba04-591070918d07.png

在当前应用中,客户登录到主页,在那里他们可以看到特色/突出显示的书籍。他们还可以选择搜索书籍项目。在获得期望的结果后,顾客可以选择书籍项目并将其添加到他们的购物车中。顾客可以在最后结账前核实图书项目。一旦客户决定结账,现有的购物车系统会将他们重定向到外部支付网关,支付您需要为购物车中的图书支付的指定金额。

如前所述,我们的应用是一个整体应用;它的结构是作为一个单元来开发和部署的。这个应用有一个仍在增长的庞大代码库。小更新需要一次部署整个应用。

在本节中,我们讨论了应用的功能概述。我们仍然需要分析和应对挑战,并为当前的挑战找到最佳解决方案。那么,接下来让我们讨论这些事情。

应对当前挑战的解决方案

业务增长迅速,因此我们决定在另外 20 个城市开设我们的电子商务网站。然而,我们仍然面临着现有应用的挑战,并努力为现有用户群提供适当的服务。在这种情况下,在我们开始过渡之前,我们应该让我们的单片应用为过渡到微服务做好准备。

在第一种方法中,购物车模块将被分离成更小的模块,然后您将能够使这些模块以及外部或第三方软件相互作用:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/736d1010-9c6e-442f-b525-5bcfaa1aa749.png

这个提议的解决方案对于我们现有的应用来说是不够的,尽管开发人员能够划分代码并重用它。然而,业务逻辑的内部处理将保持与用户界面或数据库交互的方式相同。新代码将与用户界面和数据库层交互,而数据库仍然保持为同一个旧的单一数据库。随着我们的数据库保持不可分割和紧密耦合的层,必须更新和部署整个代码库的问题仍然存在。所以,这个方案不适合解决我们的问题。

处理部署问题

在上一节中,我们讨论了当前面临的部署挑战.NET 单片应用。在本节中,让我们来看看如何通过在相同的环境中进行一些实践来克服这些挑战.NET 堆栈。

用我们的.NET 单片应用,我们的部署由 XCOPY 部署组成。在将我们的模块划分为不同的子模块后,我们可以借助这些子模块来适应部署策略。我们可以简单地部署我们的业务逻辑层或一些常见的功能。我们可以适应持续的集成和部署。XCOPY 部署是一个将所有文件复制到服务器的过程;它主要用于 web 项目。

制造更好的单片应用

现在,我们已经了解了现有单片应用的所有挑战,新的应用应该能够更好地应对新的变化。随着我们的扩张,我们不能错过获得新客户的机会。如果我们不能克服挑战,那么我们也会失去商业机会。让我们讨论几个点来解决这些问题。

引入依赖注入

我们的模块是相互依赖的,因此我们面临着一些问题,例如代码的可重用性和由于一个模块中的变化而导致的未解决的 bug。这些是部署挑战。为了解决这些问题,让我们分离我们的应用,这样我们就能够将模块分成子模块。我们可以划分我们的Order模块,让它实现接口,这可以从构造函数开始。

Dependency injection (DI) is a design pattern and provides a technique so that you can make a class independent of its dependencies. It can be achieved by decoupling an object from its creation.

下面是一个简短的代码片段,展示了我们如何将它应用到现有的整体应用中。下面的代码示例显示了我们的Order类,其中我们使用构造函数注入:

using System;
using System.Collections.Generic;
using FlixOne.BookStore.Models;

namespace FlixOne.BookStore.Common
{
    public class Order : IOrder
    {
        private readonly IOrderRepository _orderRepository;
        public Order() => _orderRepository = new OrderRepository();
        public Order(IOrderRepository orderRepository) => _orderRepository = orderRepository;
        public IEnumerable<OrderModel> Get() => _orderRepository.GetList();
        public OrderModel GetBy(Guid orderId) => _orderRepository.Get(orderId);
    }
}

Inversion of control, or IoC, is the way in which objects do not create other objects on whom they rely to do their work.

在前面的代码片段中,我们抽象了我们的Order模块,使得它可以使用IOrder接口。之后,我们的Order类实现了IOrder接口,通过使用控制反转,我们创建了一个对象,因为这是在控制反转的帮助下自动解决的。

此外,IOrderRepository的代码片段如下:

using FlixOne.BookStore.Models;
using System;
using System.Collections.Generic;

namespace FlixOne.BookStore.Common
{
    public interface IOrderRepository
    {
        IEnumerable<OrderModel> GetList();
        OrderModel Get(Guid orderId);
    }
}

我们有以下OrderRepository的代码片段,它实现了IOrderRepository接口:

using System;
using System.Collections.Generic;
using System.Linq;
using FlixOne.BookStore.Models;

namespace FlixOne.BookStore.Common
{
    public class OrderRepository : IOrderRepository
    {
        public IEnumerable<OrderModel> GetList() => DummyData();
        public OrderModel Get(Guid orderId) => DummyData().FirstOrDefault(x => x.OrderId == orderId);
        }
}

在前面的代码片段中,我们有一个名为DummyData()的方法,用于为我们的示例代码创建Order数据。

以下是显示DummyData()方法的代码片段:

private IEnumerable<OrderModel> DummyData()
        {
            return new List<OrderModel>
            {
                new OrderModel
                {
                    OrderId = new Guid("61d529f5-a9fd-420f-84a9-
                                        ab86f3eaf8ad"),
                    OrderDate = DateTime.Now,
                    OrderStatus = "In Transit"
                },
                ...
            };
        }

在这里,我们试图展示我们的Order模块是如何抽象的。在前面的代码片段中,我们为订单返回了默认值(使用示例数据),只是为了演示实际问题的解决方案。

最后,我们的表示层(MVC 控制器)将使用可用的方法,如下面的代码片段所示:

using FlixOne.BookStore.Common;
using System;
using System.Web.Mvc;

namespace FlixOne.BookStore.Controllers
{
    public class OrderController : Controller
    {
        private readonly IOrder _order;
        public OrderController() => _order = new Order();
        public OrderController(IOrder order) => _order = order;
        // GET: Order
        public ActionResult Index() => View(_order.Get());
        // GET: Order/Details/5
        public ActionResult Details(string id)
        {
            var orderId = Guid.Parse(id);
            var orderModel = _order.GetBy(orderId);
            return View(orderModel);
        }
    }
}

下图是一个类图,描述了我们的接口和类如何相互关联,以及它们如何公开它们的方法、属性等:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/362f8435-c428-4041-9bbe-df356267388a.png

在这里,我们再次使用构造函数注入,其中IOrder被传递并初始化了Order类。因此,所有的方法在我们的控制器中都是可用的。

走到这一步意味着我们已经克服了一些问题,包括:

  • 减少模块依赖:随着IOrder在我们应用中的引入,我们已经减少了Order模块的相互依赖。这样,如果我们需要在该模块中添加或移除任何东西,那么其他模块将不会受到影响,因为IOrder仅由Order模块实现。假设我们想对我们的Order模块进行增强;这不会影响我们的Stock模块。这样,我们减少了模块的相互依赖性。
  • 引入代码可重用性:如果需要获取任何应用模块的订单详细信息,可以使用IOrder类型轻松实现。
  • 代码可维护性的提升:我们现在已经把我们的模块划分成子模块或者类和接口。我们现在可以以这样一种方式来构造我们的代码,即所有的类型(也就是说,所有的接口)都放在一个文件夹下,并遵循存储库的结构。有了这种结构,我们将更容易安排和维护代码。
  • 单元测试:我们目前的单片应用没有任何一种单元测试。随着接口的引入,我们现在可以轻松地执行单元测试,并轻松地采用测试驱动开发的系统。

数据库重构

正如上一节所讨论的,我们的应用数据库非常庞大,并且依赖于单个模式。重构时应该考虑这个庞大的数据库。为了重构我们的应用数据库,我们遵循以下几点:

  • 模式修正:一般情况下(不需要),我们的模式描述了我们的模块。如前几节所述,我们庞大的数据库只有一个模式(现在是dbo ),,代码或表的每个部分都不应该与dbo相关。可能有几个模块会与特定的表交互。例如,我们的Order模块应该包含一些相关的模式名,比如Order。所以,每当我们需要使用这些表时,我们可以用它们自己的模式来使用它们,而不是一般的dbo模式。这不会影响与如何从数据库中检索数据相关的任何功能,但它会以这样一种方式来构造或排列我们的表,即我们能够识别每个表并将其与其特定模块相关联。当我们处于将单一应用转换为微服务的阶段时,这个练习将非常有帮助。参考下图描述数据库的订单模式和库存模式:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/b1eeaa2f-a8ad-4699-b861-22ace5706185.png

在上图中,我们看到了数据库模式是如何逻辑分离的。由于我们的订单模式和库存模式属于同一个数据库,因此在物理上没有分离。因此,在这里,我们将在逻辑上而不是物理上分离数据库模式。

我们也可以以我们的用户为例;并非所有用户都是管理员或属于特定的区域、地区或地区。然而,我们的用户表应该以这样一种方式来构造,即我们应该能够通过表名或它们的构造方式来识别用户。在这里,我们可以根据区域来构建我们的用户表。我们应该将用户表映射到一个区域表,这样它就不会影响或改变现有的代码库。

  • 将业务逻辑从存储过程转移到代码中:在当前的数据库中,我们有数千行存储过程,其中包含大量的业务逻辑。我们应该将业务逻辑转移到我们的代码库中。在我们的整体应用中,我们使用实体框架;在这里,我们可以避免创建存储过程,并且我们可以将所有的业务逻辑写成代码。

数据库分片和分区

说到数据库分片和分区,我们选择数据库分片。在这里,我们将把它分成更小的数据库。这些较小的数据库将部署在单独的服务器上:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/70c40b50-b9fe-4ae4-9a1b-eb199c381211.png

通常,数据库分片被简单地定义为大型数据库的无共享分区方案。这样,我们可以实现更高水平的高性能和可扩展性。分片这个词来自分片和传播,这意味着将一个数据库分成块(分片),并将其传播到不同的服务器。

Sharding can come in different forms. One would be splitting customers and orders into different databases, but one could also split customers into multiple databases for optimization. For instance, customers A-G, customers H-P, and customers Q-Z (based on surname).

上图是如何将我们的数据库划分成更小的数据库的示意图。请看下图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/577dc8fd-282b-4d05-a822-ff02bc4ebc6e.png

上图说明我们的应用现在有了一个更小的数据库,或者每个服务都有自己的数据库。

德沃普斯文化

在前几节中,我们讨论了团队面临的挑战和问题。在这里,我们将为 DevOps 团队提出一个解决方案:应该强调开发团队与另一个运营团队的协作。我们还应该建立一个开发、质量保证和基础设施团队合作的系统。

自动化

基础架构设置可能是一项非常耗时的工作,当基础架构准备就绪时,开发人员仍然处于空闲状态。他们需要一些时间才能加入团队并做出贡献。基础架构设置的过程不应该阻止开发人员提高工作效率,因为这会降低整体工作效率。这应该是一个自动化的过程。使用 Chef 或 PowerShell,我们可以轻松创建虚拟机,并在需要时快速增加开发人员数量。这样,我们的开发人员就可以在加入团队的第一天开始工作。

Chef 是一个 DevOps 工具,它提供了一个自动化和管理基础设施的框架。PowerShell 可以用来创建我们的 Azure 机器和设置 Azure DevOps(以前的 TFS)。

测试

我们将引入自动化测试,作为我们在部署期间进行测试时所面临问题的解决方案。在解决方案的这一部分,我们必须将我们的测试方法划分如下:

  • 采用测试驱动开发 ( TDD )。使用 TDD,开发人员在编写实际代码之前编写测试。这样,他们将测试自己的代码。测试是另一段代码,可以验证功能是否按预期工作。如果发现任何功能不满足测试代码,则相应的单元测试失败。这个功能很容易修复,因为你知道这就是问题所在。为了实现这一点,我们可以利用框架,如微软测试或单元测试。
  • 质量保证团队可以使用脚本来自动化他们的任务。他们可以利用 QTP 或硒框架创建脚本。

版本控制

当前的系统没有任何类型的版本控制系统,所以如果在变更期间发生了什么,就没有办法恢复。为了解决这个问题,我们需要引入一个版本控制机制。在我们的例子中,这应该是 Azure DevOps 或者 Git。通过使用版本控制,如果发现我们的更改破坏了某些功能或者在应用中引入了任何意外行为,我们现在可以恢复它。我们现在能够在个人层面上跟踪在这个应用上工作的团队成员所做的更改。然而,在我们的单片应用中,我们没有能力做到这一点。

部署

在我们的应用中,部署是一个巨大的挑战。为了解决这个问题,我们将引入持续集成 ( CI )。在这个过程中,我们需要设置一个 CI 服务器。随着 CI 的引入,整个过程实现了自动化。一旦代码被任何团队成员使用版本控制 Azure DevOps 或 Git 签入,在我们的例子中,CI 过程就开始起作用了。这确保了新代码被构建,单元测试与集成测试一起运行。在成功构建或其他情况下,团队会被告知结果。这使团队能够快速响应问题。

接下来,我们继续进行连续部署。这里,我们介绍各种环境,即开发环境、试运行环境、质量保证环境等等。现在,只要代码被任何团队成员签入,CI 就会开始工作。这将调用单元/集成测试套件,构建系统,并将其推到我们设置的各种环境中。这样,开发团队为质量保证提供合适构建的周转时间被减少到了最低限度。

作为一个整体应用,我们面临着与部署相关的各种挑战,这些挑战也会影响开发团队。我们已经讨论了配置项/光盘,并了解了部署是如何工作的。

下一节将介绍如何识别单块架构中可能导致问题的分解候选对象。

确定单片中的分解候选项

我们现在已经清楚地确定了当前 FlixOne 应用架构及其生成的代码给开发团队带来的各种问题。我们也理解开发团队不能接受哪些业务挑战,以及为什么。

这并不是团队能力不够——这只是代码。让我们继续前进,看看专注于 FlixOne 应用各个部分的最佳策略,我们需要将这些部分转移到微服务风格的架构中。我们需要知道,我们有一个具有整体架构的候选人,这在以下某个方面会带来问题:

  • 集中部署:虽然这是在整个过程的最后阶段,但它需要更多的尊重,这是理所当然的。这里需要理解的是,这个因素从识别和设计的初始阶段就塑造和定义了整个开发策略。这里有一个例子:企业要求你解决两个同等重要的问题。其中一个问题可能需要您对更多的相关模块进行测试,而另一个问题的解决方案可能允许您通过有限的测试逃脱惩罚。不得不做出这样的选择是错误的,企业不应该有这样做的选择。
  • 代码复杂度:拥有更小的团队是这里的关键。您应该能够为与单一功能相关的变更分配小型开发团队。小团队由一两个成员组成。除此之外,还需要一个项目经理。这意味着一些东西在模块之间比它应该的更加相互依赖。
  • 技术采用:你应该可以在不破坏任何东西的情况下,将组件升级到更新的版本或者不同的技术。如果你必须考虑依赖技术的组件,你有不止一个候选。即使您不得不担心这个组件所依赖的模块,您仍然会有多个候选模块。我记得我的一个客户有一个专门的团队来测试正在发布的技术是否适合他们的需求。我后来了解到,他们实际上会移植其中一个模块,并测量整个系统的性能影响、工作量要求和周转时间。不过,我不同意这一点。
  • 高资源:在我看来,一个系统中的所有东西,从内存、CPU 时间、I/O 需求,都应该被认为是一个模块。如果任何一个模块花费的时间更长,和/或出现的频率更高,就应该挑出它。在任何涉及高于正常内存的操作中,处理时间会阻塞延迟,输入/输出会让系统等待;这对我们来说是件好事。
  • 人类依赖:如果跨模块移动团队成员看起来工作量太大,那么你就有更多的候选人。开发人员很聪明,但如果他们在大型系统上挣扎,那不是他们的错。将系统分解成更小的单元,开发人员将会更加舒适和高效。

这一部分帮助我们理解了单体架构面临的问题。接下来,我们将继续讨论这种架构的一些优势。

重要的微服务优势

我们已经完成了确定迁移到微服务的候选人的第一步。仔细研究微服务提供的相应优势是值得的。让我们在以下几节中了解它们。

技术独立性

随着每个微服务相互独立,我们现在有能力为每个微服务使用不同的技术。支付网关可能使用最新的.NET 框架,而产品搜索可以转移到任何其他编程语言。

整个应用可以基于用于数据存储的 SQL Server,而库存可以基于 NoSQL。灵活性是无限的。

相互依赖消除

由于我们试图在每个微服务中实现独立的功能,因此很容易在每个微服务中添加新功能、修复 bug 或升级技术。这对其他微服务没有影响。现在,您有了垂直代码隔离,这使您能够执行所有这些操作,并且部署速度仍然一样快。

这还没有结束。FlixOne 团队现在有能力在现有支付网关的基础上发布一个新的支付网关选项。两个支付网关可以共存,直到团队和企业所有者都对报告感到满意。这就是这个建筑的巨大力量发挥作用的地方。

与业务目标保持一致

了解某些功能更难处理或更耗时并不一定是企业所有者的顾虑。他们的责任是继续推动和发展业务。开发团队应该成为实现业务目标的支持网络,而不是路障。

理解快速响应业务需求和适应营销趋势不是微服务的副产品,而是目标,这一点非常重要。

用更小的团队实现这些目标的能力只会让微服务更适合企业所有者。

成本效益

每个微服务都成为企业的投资,因为它可以很容易地被其他微服务消费,而不必一次又一次地重做相同的代码。每次重用一个微服务,都可以通过避免测试和部署该部分来节省时间。

用户体验得到增强,因为停机时间要么被消除,要么被减少到最小。

易于扩展

有了垂直隔离,每个微服务都向整个系统提供特定的服务,就很容易扩展。不仅对缩放候选对象的识别更容易,而且成本更低。这是因为我们只扩展了整个微服务生态系统的一部分。

这项工作对企业来说可能是成本密集型的。因此,业务团队现在可以选择先扩展哪个微服务的优先级;这个决定不再是开发团队的选择。

安全

安全性类似于传统分层架构提供的安全性;微服务可以同样容易地得到保护。不同的配置可以用来保护不同的微服务。您可以将微服务生态系统的一部分放在防火墙后面,另一部分用于用户加密。面向 Web 的微服务的安全性可以不同于其他微服务。您可以根据选择、技术或预算来满足您的需求。

数据管理

在大多数单一应用中,拥有一个数据库是很常见的。几乎总是有一个数据库架构师或指定的所有者负责其完整性和维护。任何需要更改数据库的应用增强都必须通过这条路径。对我来说,这从来都不是一件容易的事情。这进一步减缓了应用增强、可伸缩性和技术采用的过程。

因为每个微服务都有自己的独立的数据库,所以与数据库中所需更改相关的决策可以很容易地委托给各自的团队。我们不必担心对系统其他部分的影响,因为不会有任何影响。

同时,数据库的这种分离带来了团队变得自组织的可能性。他们现在可以开始实验了。

例如,团队现在可以考虑使用 Azure Table 存储或 Redis 的 Azure Cache 来存储大量的产品目录,而不是数据库,就像目前正在做的那样。团队现在不仅可以进行实验,而且他们的经验也可以很容易地在整个系统中复制,就像其他团队以他们方便的时间表的形式所要求的那样。

事实上,没有什么能阻止 FlixOne 团队现在的创新和同时使用多种可用技术,然后比较现实世界中的性能并做出最终决定。一旦每个微服务都有了自己的数据库,FlixOne 就会变成这样:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/e01ee4a8-17b1-43ce-a338-42dd332565ae.png

在上图中,每个服务都有自己的数据库,并且具有可伸缩性;清单服务有缓存(Redis 服务器)。

将单片应用与微服务集成

无论何时选择从单一架构转向微服务风格的架构,该计划的时间和成本轴都会带来一些阻力。业务评估可能会禁止移动整体应用中没有为过渡提供业务案例的部分。

如果我们从一开始就开发应用,情况会有所不同。然而,在我看来,这也是微服务的力量。对整个单芯片架构的正确评估可以安全地识别随后要移植的单芯片器件。

我们必须防范集成的风险,以确保这些孤立的部分不会在未来给其他微服务带来问题。

虽然我们讨论了单块应用的各个部分,但我们的目标是协作创建它们,以便它们可以在基于微服务架构风格的应用遵循的模式上相互通信。为了实现这一点,需要开发原始单片应用的各种模式和技术堆栈。例如,如果我们已经使用了事件驱动的模式,我们的整体应用应该遵循这种模式,这样它就可以消费和发布事件。为了实现或遵守这种模式,我们应该管理我们的单片应用的代码,这基本上包括对现有代码进行更改的开发工作。

同样,如果需要使用应用编程接口网关模式,那么我们应该确保我们的网关应该足够了,这样它就可以与整体应用进行通信。要实现这一点可能有点复杂或棘手,因为现有的单一应用不具备公开网络服务(RESTful)的功能。这也会给开发团队带来压力,要求他们对现有代码进行修改,使应用符合网关的标准。将有很大的机会进行代码更改来添加或更新 RESTful 服务,因为这些服务很容易被网关使用。为了克服这种负担,我们可以创建一个单独的微服务,这样我们就可以避免源代码中的重大变化。

在这一节中,我们借助各种方法讨论了单片应用的集成,例如领域驱动模式、API Gateway 模式等。下一节讨论 Azure 服务结构。

Azure 服务结构概述

当我们谈论微服务时.NET Core 世界,Azure 服务结构是一个广泛用于微服务的名称。在本节中,我们将讨论结构服务。

这是一个帮助我们轻松打包、部署和管理可扩展且可靠的微服务的平台(容器也类似于 Docker)。由于复杂的基础设施问题,有时很难专注于作为开发人员的主要职责。在 Azure 服务结构的帮助下,开发人员无需担心基础设施问题。Azure 服务结构提供了各种技术,并作为一个捆绑包提供,具有 Azure SQL 数据库、宇宙数据库、微软 Power BI、Azure 事件中心、Azure 物联网中心和许多其他核心服务的功能。

根据官方文档(https://docs . Microsoft . com/en-us/Azure/Service-Fabric/Service-Fabric-overview),我们可以对 Azure Service Fabric 进行如下定义:

  • 服务结构—任何操作系统、任何云:您只需要创建一个服务结构集群,它将在 Azure(云)或内部、Linux 或 Windows 服务器上运行。此外,您还可以在其他公共云上创建集群。
  • 服务结构:无状态和有状态微服务。在服务结构的帮助下,您可以将应用构建为无状态或有状态的。

根据其官方文档(https://docs.microsoft.com/en-us/azure/service-fabric/),我们可以如下定义无状态微服务:

“Stateless microservices are not in the mutable state outside a request and its response from the service, for example Azure Cloud Services worker roles (Cloud Services should be avoided during development of new services). Stateful microservices are in a mutable state beyond the request and its response.”

对应用生命周期管理的全面支持:在服务结构的帮助下,获得完整应用生命周期的支持,包括开发、部署等。

您可以开发一个可扩展的应用。更多信息请参考:https://docs . Microsoft . com/en-us/azure/service-fabric/service-fabric-application-life cycle。

您可以开发高度可靠、无状态和有状态的微服务。

Cloud Services should be avoided during the development of new services.

有不同的服务结构编程模型,超出了本章的范围。更多信息请参考:https://docs . Microsoft . com/en-us/azure/service-fabric/service-fabric-choose-framework。

本节的目的是概述 Azure 服务结构,然后介绍无状态微服务。我们已经看到 Azure 服务结构支持开发可扩展的应用。

摘要

在这一章中,我们详细讨论了微服务架构风格、它的历史,以及它与它的前辈、单体架构和 SOA 的不同之处。我们进一步定义了单块架构在处理大型系统时面临的各种挑战。可伸缩性和可重用性是 SOA 相对于单一架构的一些明显优势。

我们还讨论了单片架构的局限性,包括通过实现真实的单片应用来扩展问题。微服务架构风格通过减少代码相关性和隔离任何一个微服务处理的数据集大小来解决所有这些问题。为此,我们利用了依赖注入和数据库重构。我们还进一步探索了自动化、配置项和部署。这些很容易让开发团队让业务发起人选择首先响应哪个行业趋势。这将带来成本效益、更好的业务响应、及时的技术采用、有效的扩展以及消除人员依赖。最后,我们讨论了 Azure 服务结构,并了解了服务结构及其不同的编程模型。

在下一章中,我们将继续将我们现有的应用转换为微服务风格的架构,并对我们的知识进行测试。我们将通过讨论新的技术堆栈(C#、EF 等)将我们的单片应用转换为微服务。我们还将介绍 seam 的概念,并讨论微服务通信。

问题

  1. 什么是微服务?
  2. 您能定义 Azure 服务结构吗?
  3. 什么是数据库分片?
  4. 什么是 TDD,开发者为什么要采用这个?
  5. 你能详细说明依赖注入(DI)吗?

二、重构整体

在前一章中,我们讨论了分层整体建筑的问题。本章旨在讨论单片到基于微服务的应用的过渡。我们将在这一章开始讨论我们的新技术堆栈(C#和实体框架),然后讨论好服务的特性,以及我们如何从现有系统中重构它们,并为产品和订单构建单独的微服务。

我们将涵盖以下主题:

  • 了解当前和新的技术堆栈
  • 微服务的规模
  • 什么是好的服务?
  • 理解接缝的概念
  • 微服务之间的通信
  • 重新审视 FlixOne 案例研究

技术要求

本章包含解释这些概念的各种代码示例。代码保持简单,仅用于演示目的。

要运行和执行代码,以下是先决条件:

  • Visual Studio 2019
  • 。网络核心 3.1

要运行这些代码示例,您需要安装 Visual Studio 2019(首选的 IDE)。为此,请从安装说明中提到的下载链接下载 Visual Studio 2019:https://docs . Microsoft . com/en-us/Visual Studio/install/install-Visual Studio。有多个版本的 Visual Studio 可供安装,包括免费版本的 Visual Studio Community。我们使用的是视窗操作系统。

如果你没有.NET Core 3.1 安装完毕,可以从这个链接下载:https://www.microsoft.com/net/download/windows。

The complete source code is available here: https://github.com/PacktPublishing/Hands-On-Microservices-with-CSharp-8-and-.NET-Core-3-Third-Edition/tree/master/Chapter%2002

了解当前和新的技术堆栈

在我们继续讨论实现微服务的概念之前,值得一提的是我们将用来实现这些微服务的核心概念、语言和工具。在本章中,我们将对这些主题进行概述。

讨论–当前堆栈(C#、EF 和 SQL Server)

在本节中,我们将讨论我们在演示应用中使用的技术堆栈。

C# 8

C#是微软开发的一种编程语言,写这本书时的当前版本是 C# 8。这种面向对象和面向组件的语言始于 2002 年。当前版本有各种新特性,如 ValueTuple、解构器、模式匹配和 switch 语句本地函数。

我们不会详细讨论这些特性,因为这超出了本书的范围。详见https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/。

实体框架核心

实体框架核心 ( EF 核心)是微软实体框架的跨平台版本,是目前最流行的对象关系映射器 ( 表单)之一。

ORM is a technique that helps you to query and manipulate data, according to the required business output.

英孚核心支持各种数据库。这里有完整的数据库列表:https://docs.microsoft.com/en-us/ef/core/providers/index。EF Core 的当前版本(撰写本文时)为 3.1(https://devblogs . Microsoft . com/dotnet/公告-实体-框架-Core-3-1-和-实体-框架-6-4/ )。

Visual Studio 2019

Visual Studio 是最好的 IDEs 之一,由微软创建。它使开发人员能够使用流行的语言以各种方式工作,如 C#、VB.NET 和 F#。Visual Studio 2019 当前版本为 VS16.4(https://docs . Microsoft . com/en-us/visualstudio/releases/2019/releases-notes)。要找到最新版本的 Visual Studio,请查看官方页面:https://docs . Microsoft . com/en-us/Visual Studio/install/update-Visual Studio?view=vs-2019 。

An IDE is an integrated development environment, a software application that provides a facility for programmers to write programs by using programming languages. For more information, visit https://en.wikipedia.org/wiki/Integrated_development_environment.

微软也为 macOS 发布了 Visual Studio,Visual Studio 有很多新功能。更多信息请参考https://www.visualstudio.com/vs/whatsnew/。在这本书里,所有的例子都是用 Visual Studio 2019 写的。您也可以在以下链接下载免费的社区版:https://www.visualstudio.com/.

搜寻配置不当的

微软 SQL Server ( MSSQL )是一个软件应用,是一个关系数据库管理系统。它主要用作数据库软件来存储和检索数据;它建立在结构化查询语言(SQL):http://searchsqlserver.techtarget.com/definition/SQL之上。

当前版本——即 SQL Server 2019——更加健壮,可以在 Windows 和 Linux 上使用。您可以从这里获得 SQL Server 2019:https://www.microsoft.com/en-IN/sql-server/sql-server-2019。请注意,我们将在本书中使用 SQL Server 2008 R2 或更高版本。

在开始阅读本书的实际操作说明之前,让我们先了解一下中的新功能.NET Core 和 C# 8.0,我们将在本书中利用它们。以下两个部分更像是对中新特性的探索.NET Core 3.1 和在 C# 8.0 中的新特性。我们将为这些特性添加深入的细节和说明。如果您已经了解新功能,可以跳过这些部分。

中的新功能.NET Core

在前一节中,我们概述了当前的技术堆栈。本节和下一节的目的是简要介绍即将推出的技术堆栈的预期特性.NET Core 3.1 和 C# 8.0),并利用我们的代码示例中的新功能。请注意,在写这本书的时候.NET Core 3.1 刚刚发布。

的释放.NET Core 3.1 是一个小版本,由早期版本的修复组成.NET Core 3.0。因此,在本节中,我们将重点讨论的新功能.NET Core 3.0。

.NET Core 3.1 is announced as a Long Term Support (LTS) release for three years.

.NET Core 3.0 应该会让桌面应用的开发人员兴奋不已。这个版本带来了最大的增强,现在它支持 Windows 桌面应用的开发。目前,此增强功能仅适用于在 Windows 机器上工作的用户,因为支持仅限于 Windows。支持包括开发视窗窗体和视窗演示基金会(WPF)应用。

我想你已经安装了.NET Core 3.1;如果还没有,请重访技术要求部分。

验证的安装.NET Core 3.1,可以查看当前安装版本的信息.NET Core。为此,请打开命令提示符并键入以下命令:

dotnet --info

前面的命令将告诉您有关已安装的的完整信息.NET Core 版本,如下图截图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/467a344a-d3b9-4def-bd9e-fd65a9486358.png

前面的截图显示了所有可用的信息.NET Core 版本。我们版本的.NET Core 是 3.1.100。

现在让我们看看如何利用我们之前提到的新功能。如果您想开发 Windows 桌面应用,那么您可以使用命令行界面 ( 命令行界面 ) dotnet命令或 Visual Studio 2019 来创建 WPF 和 Windows 窗体应用。

要从命令行界面开始,请执行以下步骤:

  1. 打开您的命令提示符或 Windows PowerShell(我们将使用 PowerShell)。
  2. 转到您想要的文件夹(在那里您想要创建一个新项目)——在我们的例子中,我们将使用Chapter02文件夹。
  3. 传递以下命令创建一个 Windows 窗体应用:
dotnet new winforms

前面的代码片段如下:

  • dotnet new:这将初始化一个有效的.NET Core 项目。
  • winforms:这是一个在初始化有效时被调用的模板.NET Core 项目。

If you want to use the VB language instead of C#, you just need to pass the preceding command with the language as dotnet new winforms -lang VB.

使用WinForms模板处理前面的命令并创建一个 Windows 窗体应用。下面的截图描述了项目步骤的创建:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/c0948f9c-5b2b-49dc-8faf-ab6b23511501.png

我们已经创建了一个WinForms应用。要运行此项目,请传递以下命令:

dotnet run

前面的命令将运行项目并显示项目的输出。我们没有添加任何内容,因此我们将看到空白的 Windows 窗体,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/c33c6325-3491-4c9c-b6ef-204488aeac3d.png

  1. 传递以下命令创建一个 Windows 窗体应用:
dotnet new wpf

前面的命令使用wpf模板创建了一个 WPF 应用。下面的截图显示了创建应用的步骤:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/a8833432-2441-4f5b-9ddf-20e23500ecd7.png

我们已经创建了一个wpf应用。要运行此项目,请传递以下命令:

dotnet run

前面的命令将运行项目并显示项目的输出。我们没有添加任何内容,因此我们将看到空白的 WPF 表单,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/593b50a1-1863-4251-9312-faeef46dc28e.png

可以使用更多带有dotnet new命令的模板;如果您不确定模板名称,只需传递以下命令:

dotnet new -l

前面的命令以表格形式给出了所有可用模板的列表。请参考下面的截图,其中显示了可用的模板:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/4a82b134-7915-4c75-aa3c-dedefebb73a6.png

前面的截图显示了可用的预安装模板;如果要安装任何附加模板,可以使用dotnet new -i <Tempate>命令。上一个快照包含以下内容:

  • Templates:这是模板的完整名称。
  • Short Name:这是与dotnet new命令一起使用的模板名称。
  • Language:这是支持模板的编程语言。模板的默认语言显示在括号[ ]中。
  • Tags:表示模板的类型。例如,Web标签指的是 ASP.NET Core(网络)项目模板。

dotnet new命令有各种选项,如下表所示:

| [计]选项 | 描述 | 句法 |
| -h、--help | 这为命令提供了帮助,并提供了关于命令及其使用的信息。 | dotnet new -h |
| -l、--list | 这给出了模板的完整列表。 | dotnet new wpf -l
dotnet new -l |
| -n、--name | 这是使用特定模板创建的项目/应用名称。如果未指定名称,则使用当前文件夹的名称。 | dotnet new wpf -n "MyProjName" |
| -i、-install | 这将安装一个新模板。 | dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0 |
| -lang、--language | 这指定了在其中创建项目的语言。 | dotnet new winforms -lang VB |

如果您使用的是 VS16.4,并且有早期版本的.NET Core 3.0 和/或.NET Core3.1 安装后,您可能会看到以下屏幕。在这种情况下,版本、WinForms和WPF设计器支持不可用。如果您尝试打开我们之前创建的项目,您会注意到一个设计器错误,如下图所示。当您尝试打开窗体 1 的设计布局时,会出现此错误:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/55d37c78-699d-407c-ba1c-1aa661a8291b.png

上一张截图中显示的问题可以在经典设计师的帮助下解决.NET 框架)。通过这种变通方法,我们可以看到一个设计者,并可以安排或设计我们的表单布局。这个话题超出了本书的范围,我们就不多赘述了。

To get more details and to know how we can fix the designer issue, you can refer to the official GitHub repository: https://github.com/dotnet/winforms/blob/master/Documentation/winforms-designer.md.

此修复程序随 Visual Studio Preview 一起提供(在撰写本书时);您至少需要 VS16.5 才能查看 WinForms 或 WPF 的设计器。您可以通过安装 VS16.5 进行检查。要确保您从 Visual Studio 选项中标记了预览 SDK,请转到工具|选项|环境|预览功能中的设置,然后确保您选中了使用预览窗口窗体设计器进行.NET Core 应用,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/7251485f-ba26-4ebd-82b2-67179cf7c641.png

您需要重新启动 Visual Studio,打开 Windows 项目,然后在要在设计器中打开的文件名上单击“Shift+F7”。

C# 8.0 中的新特性

在本节中,我们将讨论 C# 8.0 中尚未发布的新特性(在撰写本书时)。在我们的代码示例中,我们使用了 C# 8.0 的最新预览版。

在开始讨论新特性之前,我们应该首先启用对 C#8.0 语言的支持。

使用 Visual Studio 支持 C# 8.0 语言

如果没有最新安装的 Visual Studio(参考技术要求,按照以下步骤启用对 C# 8.0 的支持:

打开项目属性,然后打开构建选项卡,然后单击高级…按钮,靠近页面底部。选择 C# 8.0(测试版),如下图所示,或者从“常规”组下的“语言版本”下拉列表中选择任何最新版本。下面的截图说明了每一步:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/1dbb0b11-a0d8-4c76-be02-c2f6a6096b1a.png

如果安装了最新版本的 Visual Studio,您将看到以下窗口:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/4877f5cd-4ec4-4e37-82bb-3d2564bd689d.png

根据前面的截图,Visual Studio 不允许我们更改/选择 C#版本。这是因为已经选择了 C#版本。C#编译器已经根据我们项目的目标框架为我们确定了默认语言版本。

下表显示了编译器如何选择默认版本:

| 目标框架 | C#语言版本 |
| .NET Core 3.x | Eight |
| .NET Core 2.x | Seven point three |
| .NET 标准(所有版本) | Seven point three |
| .NET 框架(所有版本) | Seven point three |

If a project targets a preview framework, then the compiler accepts all of the language syntaxes from the latest preview version.

现在,我们讨论一下 C# 8.0 的新特性。

指数和范围

作为一项新功能,现在开发人员可以根据数组的索引或指定范围轻松获取数组的元素值。这可以通过引入两个运算符来实现:^运算符代表从末尾开始的索引,..运算符代表范围。下面的截图代表了我们的一系列图书名称:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/a89a8dda-0e12-4f3b-9fdd-056f85649f27.png

上一张截图显示了Books []每个元素的值,以及索引位置。

To showcase the feature, we have created a console app, using Visual Studio 2019.

在早期版本的 C#中,只能通过数组的第一个索引获取数组第一个元素的值;在这种情况下,我们将编写以下代码:

Console.WriteLine($"\tFirst element of array (index from start): Books{{0}} => {Books[0]}");

借助 C# 8.0 的一个新特性,现在可以使用^运算符获取元素值,它从末尾开始取索引和计数。我们的Books[]阵总长度为6。因此,为了获得第一个元素的值,我们将编写以下代码:

Console.WriteLine($"\tFirst element of array (index from end): Books{{^6}} => {Books[^6]}");

现在,运行代码,您将获得如下截图所示的输出:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/0ff4b89b-d9ee-43a5-ad3e-99bbe35f7b5e.png

在早期版本的 C#中,当我们需要一个数组的所有元素的值时,我们必须迭代数组的元素,从第 0 第 个索引开始,直到数组的长度。在 C# 8.0 中,当我们需要打印一个数组的值时,我们可以使用一个范围,在从开始索引值到结束索引值的范围内。在..算子的帮助下,现在我们可以这样得到Book []的值:Book [1..4]。

为了使用这个特性,让我们编写以下代码:

//Print using range
Range book = 1..4;
var res =  Books[book] ;
Console.WriteLine($"\tElement of array using Range: Books[{book}] => {Books[book]}");

在前面的代码中,我们已经声明了一个范围值为1到4的book变量,然后我们使用该变量打印范围内Books[]的值。运行代码并查看输出;输出显示在下面的截图中:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/00f164e2-e54b-485a-a707-ea4fe67bb047.png

在上一个截图的高亮行中,你可能已经注意到我们的输出不如预期;我们得到System.String[]作为输出,但是我们期待数组的值。前面的输出是正确的——我们将Books[]的值存储在一个新的变量book中,并且我们必须迭代该变量来获得值。

要验证这一点,请在调试模式下运行前面的代码(从 Visual Studio 2019 开始),方法是按下 F5 功能键或使用菜单选项:调试|开始调试。确保您在正确的位置放置了一个断点来查看调试结果。以下截图是在调试过程中捕获的:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/5925d7f9-36bb-43cf-b8a1-73c4fb235d38.png

为了获取这些值,我们修改了前面的代码,下面是获取输出的代码:

//Print using range
Range book = 1..4;
var books =  Books[book] ;
Console.WriteLine($"\n\tElement of array using Range: Books[{book}] => {books}\n");
foreach (var b in books)
{
    Console.WriteLine($"\t{b}");
}    

现在,运行前面的代码,您应该会看到如下截图所示的输出:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/2d298106-b7d0-4b34-b2ad-c381bd814abb.png

根据我们的范围,前面的截图给出了准确的输出。有更多的指数和范围场景,但我们没有涵盖所有内容,因为它不在本书的范围内。

只读成员

有了这个特性,我们现在可以在结构中定义一个readonly成员。这意味着该成员不能被修改,也不能从任何成员函数中更改。static readonly成员的值只能在运行时从静态构造函数更改。readonly字段不可修改的重要性在于,它将有助于避免整个代码中的任何意外更改或错误分配。这在我们有默认值并希望在运行时更改字段的情况下也很重要。

以下代码定义了readonly成员:

private static readonly int num1=5;
private static readonly int num2=6;

前面的代码声明了两个预先赋值的readonly成员。您可以编写一个方法来使用这些成员的值——类似于public static int Add => num1 + num2;。

运行代码,您应该会看到如下截图所示的输出:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/f8b6c124-38b7-4478-94bd-24e9f1dea6ff.png

前面的截图显示了我们Add方法的输出。此方法不应声明为只读,否则会给你一个错误。例如,以下代码会给您一个错误:

public readonly static int Add => num1 + num2;

前面的代码不起作用,如果您构建代码,它会给您一个错误。考虑以下截图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/e1662d07-a7d5-48ee-988b-ee815260f4b9.png

您将看到一条消息,如前面的截图所示。

默认接口方法

有了这个特性,现在,开发人员可以在接口中添加带有主体的方法。之前我们必须在后续的类中显式地编写接口方法的功能。但是现在,我们将获得这些默认接口方法的优势,当这些方法在后续类中被调用时,我们可以从中获得预定义的结果。为了更好地理解这个特性,请考虑下面的代码:

public interface IProduct
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string ProductDesc() => $"Book:{Name} has Price:{Price}";
}

在前面的代码中,我们声明了一个IProduct接口,它有以下内容:

  • Id:这是唯一的产品 ID。
  • Name:这是产品名称。
  • Price:这是产品价格。
  • ProductDesc:这是我们界面的默认方法,总是会产生结果;在我们的例子中,它将返回string作为这个方法的结果。此外,该方法适用于所有实现IProduct接口的类。

让我们在Product类上实现IProduct接口。考虑以下代码:

public class Product : IProduct
{
    public Product(int id, string name, decimal price)
    {
        Id = id;
        Name = name;
        Price = price;
    }
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

除了IProduct接口的属性之外,我们的Product类没有定义任何额外的东西。这个类中没有额外的方法。ProductDesc()方法适用于本课程。考虑下面的代码,让我们在代码中使用这个默认方法:

IProduct product = new Product(1, "Design Patterns", 350.00M);
Console.WriteLine($"\t{product.ProductDesc()}");

在前面的代码中,我们声明了一个IProduct类型的product变量,然后简单地调用了ProductDesc()方法。这将产生如下截图所示的输出:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/7e812cd4-f3c3-4100-8304-646043a72529.png

在IProduct接口中定义的ProductDesc()方法适用于我们的Product类。

在本节中,我们讨论了 C# 8.0 的一些特性。请注意,我们还添加了已随 C# 8.0 预览版 5 发布的功能。然后我们使用 C#8.0 更新了特性;在 C# 8.0 的正式版本中,有可能会排除一些功能,或者添加更多功能。在这里,我概述了 C# 8.0 的预期特性,但是为了完整地引用这些特性,我建议查看 C#语言的官方文档。

现在,让我们详细看看微服务的规模,以及这如何有助于构建它们。

微服务的规模

在我们开始构建我们的微服务之前,我们应该清楚它们的一些基本方面,例如在调整我们的微服务时要考虑哪些因素,以及如何确保它们与系统的其他部分隔离开来。

顾名思义,微服务应该是微的。但是什么是微?微服务都是关于大小和粒度的。为了更好地理解这一点,让我们考虑一下在第 1 章微服务简介中讨论的应用。

我们希望在这个项目上工作的团队在他们的代码方面始终保持同步。当我们发布完整的项目时,保持同步更加重要。我们首先需要将我们的应用及其特定部分分解成主服务的更小的功能/部分。让我们讨论微服务高级隔离需要考虑的因素:

  • 需求变化带来的风险:一个微服务的需求变化应该独立于其他微服务。在这种情况下,我们将把我们的软件隔离/分割成更小的服务,这样,如果一个服务中有任何需求变化,它们将独立于另一个微服务。
  • 功能改变:我们将把很少改变的功能从可以频繁修改的依赖功能中分离出来。例如,在我们的应用中,客户模块通知功能很少会改变。然而,其相关模块,如Order,更有可能在其生命周期中有频繁的业务变更。
  • 团队变化:我们还应该考虑以一个团队可以独立于所有其他团队工作的方式隔离模块。如果让一个新开发人员富有成效的过程——关于这些模块中的任务——不依赖于团队之外的人,这意味着我们处于有利地位。
  • 技术变化:技术使用需要在每个模块内部垂直隔离。一个模块不应依赖于另一个模块的技术或组件。我们应该严格隔离在不同技术或堆栈中开发的模块,或者将它们转移到一个公共平台作为最后的手段。

我们的主要目标不应该是使服务尽可能小。相反,我们的目标应该是隔离已识别的有界上下文,并保持它很小。

现在我们知道了微服务的大小。接下来,让我们学习什么是好的服务。

什么是好的服务?

在微服务被概念化之前,每当我们想到企业应用集成,中间件看起来是最可行的选择。软件供应商提供了企业服务总线 ( ESB ,这是中间件的最佳选择之一。

除了考虑这些解决方案,我们的主要优先事项应该是架构特性。当微服务到来时,中间件不再是一个考虑因素。相反,重点转移到业务问题的思考以及如何借助架构解决这些问题。

为了使服务易于开发人员和用户使用和维护,服务必须具有以下特征(我们也可以将这些视为好服务的特征):

  • 标准数据格式:好的服务应该遵循标准化的数据格式,同时与其他组件交换服务或系统。中使用的最流行的数据格式.NET 栈是 XML 和 JSON。
  • 标准通信协议:好的服务应该遵守标准的通信格式,比如 SOAP 和 REST。
  • 松耦合:好的服务最重要的一个特点就是跟松耦合。当服务松散耦合时,我们不必担心变化。一项服务的更改不会影响其他服务。

本节帮助我们了解什么使服务变得好以及如何变得好。我们接下来将讨论领域驱动设计及其对微服务的重要性。

DDD 及其对微服务的重要性

领域驱动设计 ( DDD )是设计复杂系统的方法和过程。在本节中,我们将简要讨论 DDD 以及它在微服务环境中的重要性。

领域模型设计

领域设计的主要目标是理解确切的领域问题,然后起草一个可以用任何语言或一组技术编写的模型。例如,在我们的 FlixOne 书店应用中,我们需要了解订单管理和库存管理。

以下是领域驱动模型的一些特征:

  • 一个领域模型应该关注一个特定的业务模型,而不是多个业务模型。
  • 它应该是可重用的。
  • 它应该被设计成可以以松散耦合的方式调用,而不像系统的其他部分。
  • 它应该独立于持久性实现进行设计。
  • 它应该从一个项目中被拉到另一个位置,所以它不应该基于任何基础设施框架。

接下来,让我们看看这个模型对微服务有多重要。

微服务的重要性

DDD 是蓝图,可以通过微服务来实现。换句话说,一旦 DDD 完成,我们就可以使用微服务来实现它。这就像在我们的应用中,我们可以轻松地实现订单服务、库存服务、跟踪服务等等。

一旦你满意地处理了过渡过程,就应该进行一个简单的练习。这将帮助您验证微服务的大小是否足够小。每个系统都是独一无二的,有自己的复杂程度。考虑到您的域的这些级别,您需要有一个可以相互对话的域对象的最大数量的基线。如果有任何服务没有达到这个评估标准,那么您就有可能再次评估您的过渡。然而,不要带着特定的数字进入这个练习;你总是可以放松。只要你正确地遵循了所有的步骤,系统对你来说应该没问题。

如果您觉得这个基线过程对您来说很难实现,您可以通过遍历每个微服务中的所有接口和类来走另一条路线。考虑到我们已经遵循的所有步骤,以及行业标准的编码准则,任何对系统不熟悉的人都应该能够理解它的目的。

您还可以执行另一个简单的测试来检查是否实现了服务的正确垂直隔离。您可以部署它们中的每一个,并让它们与仍然不可用的其余服务一起使用。如果你的服务上线了,并继续监听收到的请求,你可以拍拍自己的背。

隔离部署能力可以带来许多好处。独立部署它们的能力允许它们中的主机进入自己的独立进程。它还允许您利用云的力量以及您能想到的任何其他混合托管模式。你也可以自由选择不同的技术。

了解了 DDD 之后,我们现在可以继续讨论 seam 的概念。

理解接缝的概念

微服务的核心是独立于系统的其他部分来处理特定功能的能力。这转化为前面讨论的所有优势,例如减少模块依赖性、代码可重用性、更容易的代码维护和更好的部署。

在我看来,在微服务的实现过程中,应该保持相同的属性。为什么将单片迁移到微服务的整个过程会很痛苦,而且不如使用微服务本身有回报?请记住,过渡不能一蹴而就,需要精心规划。许多有能力的解决方案架构师在展示他们的高能力团队时有不同的方法。答案不仅在于已经提到的几点,还在于企业本身的风险。

这是完全可以实现的。然而,我们必须正确识别我们的方法来实现它。否则,将单一应用转换为微服务的整个过程可能会非常糟糕。让我们看看这是如何做到的。

模块相互依赖

当试图将一个单一的应用转换成一个微服务风格的架构时,这应该永远是一个起点。识别并挑选应用中其他模块最不依赖的部分,也是最不依赖它们的部分。

理解这一点非常重要,通过识别应用的这些部分,您不只是试图挑选最不具挑战性的部分来处理。但是,与此同时,您已经确定了接缝,这是最容易看到的变化部分。这些是应用的一部分,我们将首先在其中执行必要的更改。这使我们能够将这部分代码与系统的其他部分完全隔离开来。它应该准备好成为微服务的一部分,或者在本练习的最后阶段进行部署。

尽管已经发现了这些漏洞,但实现微服务式开发的能力还有点远。在这一节中,我们介绍了 seam 的概念,然后我们看到了什么是模块相互依赖。这是一个好的开始。然而,基于微服务的应用需要更多的模式,我们需要了解这项技术。在接下来的部分,我们将讨论这项技术。

技术

这里需要双管齐下。首先,您必须确定应用基础框架的哪些不同特性正在被利用。例如,这种区别可以是基于对某些数据结构、正在执行的进程间通信或报告生成活动的严重依赖的实现。这是容易的部分。

然而,作为第二步,我建议您变得更加自信,并选择使用不同于当前使用的技术类型的代码片段。例如,可能有一段代码依赖于简单的数据结构或基于 XML 的持久性。在系统中识别此类行李,并将其标记为过渡。这种双管齐下的方法需要非常谨慎。做出过于雄心勃勃的选择可能会让你走上一条类似于我们一直试图完全避免的道路。

对于最终的微服务风格的架构应用来说,其中一些部分可能看起来不太有希望。现在仍然应该处理它们。最终,它们将允许您轻松执行过渡。技术和框架在任何应用的开发中都起着重要的作用,但是当我们处理基于微服务架构风格的应用时,技术是最重要的。

这一节解释了技术;在下一节中,我们将讨论团队结构。

团队结构

随着识别过程的每一次迭代,这个因素变得越来越重要。可能会有基于不同理由的不同团队,例如他们的技术技能、地理位置或安全要求(员工对自由职业者)。

如果有一部分功能需要特定的技能组合,那么您可能会考虑另一个可能的 seam 候选人。团队可以由不同程度的这些分化因素组成。作为向微服务过渡的一部分,使他们能够独立工作的明显差异可以进一步优化他们的生产力。

这也可以以保护公司知识产权的形式提供好处;将应用的特定部分外包给顾问并不少见。允许顾问或合作伙伴仅在特定模块上为您提供帮助的能力使流程更加简单和安全。

团队及其成员对于任何应用的执行都非常重要。在基于微服务的应用中,我们为团队提供了灵活性,任何人都可以在不干扰他人任务或活动的情况下处理单个服务。为了了解这是如何发生的,在下一节中,我们将讨论数据库。

数据库

任何企业系统的核心和灵魂都是它的数据库。这是系统在任何一天的最大资产。在这样的演习中,它也是整个系统中最脆弱的部分。(难怪每当你要求数据库架构师做哪怕是最小的更改时,他们听起来都很刻薄和碍眼。)它们的域由数据库表和存储过程定义。

它们的域的健康状况也是由引用完整性和执行各种事务所需的时间来判断的。我不再责怪建筑师做得过头了。他们这样做是有原因的:他们过去的经历。是时候改变这种状况了。让我告诉你,这并不容易,因为当我们走上这条路时,我们将不得不使用一种完全不同的方法来处理数据完整性。

您可能会认为最简单的方法是一次性划分整个数据库,但事实并非如此。这可能会导致我们一直试图避免的局面。让我们看看如何更有效地进行这项工作。

在模块依赖关系分析之后,当您继续挑选片段时,请确定用于与数据库交互的数据库结构。这里需要执行两个步骤。首先,检查是否可以隔离代码中要分解的数据库结构,然后将其与新定义的垂直边界对齐。其次,确定需要什么来分解底层数据库结构。

如果分解底层数据结构似乎很困难,也不要担心。如果它似乎涉及到您还没有开始转移到微服务的其他模块,这是一个好的迹象。不要让数据库的变化定义了您将挑选并迁移到微服务风格架构的模块;反过来说。这确保了当数据库发生变化时,依赖于变化的代码已经准备好吸收变化。

这确保了当您已经忙于修改依赖于这部分数据库的代码时,您不会再挑起数据完整性的战斗。然而,这样的数据库结构应该引起您的注意,以便接下来选择依赖于它们的模块。这将允许您一次轻松完成所有相关模块向微服务的转移。参考下图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/7297cd64-263d-4f35-8b42-2771a5bad883.png

这里,我们还没有破坏数据库。相反,作为第一步的一部分,我们已经简单地将数据库访问部分分成了几层。

我们在这里所做的是将代码数据结构映射到数据库,以便它们不再相互依赖。让我们看看当我们移除外键关系时,这一步将如何进行。

如果我们可以将用于访问数据库的代码结构与数据库结构一起转换,我们将节省时间。这种方法可能因系统而异,并且会受到我们个人偏见的影响。如果您的数据库结构变化似乎正在影响尚未标记为过渡的模块,现在就继续。

当您分解这个数据库表或将其与另一个部分结构合并时,您还需要了解什么样的更改是可以接受的。最重要的是不要回避打破那些外来的关键关系。这听起来可能与我们维护数据完整性的传统方法有很大不同。

然而,在重组数据库以适应微服务架构时,移除外键关系是最基本的挑战。请记住,微服务意味着独立于其他服务。如果与系统的其他部分存在外键关系,这就使得它依赖于拥有该部分数据库的服务。参考下图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/89147721-fbef-41d6-b123-ba1b4e8b976f.png

作为第二步的一部分,我们保留了数据库表中的外键字段,但删除了外键约束。因此, ORDER 表仍然保存着关于 ProductID 的信息,但是外键关系现在被破坏了。参考下图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/69f280f3-15b7-4521-b813-78e2739bf563.png

这就是我们的微服务式架构最终的样子。中央数据库将被移走,取而代之的是每个服务都有自己的数据库。因此,通过分离代码中的数据结构和移除外键关系,我们已经准备好最终进行更改。上图中微服务的连接边界表示服务间的通信。

执行了这两个步骤后,您的代码现在可以将 ORDER 和 PRODUCT 拆分为单独的服务,每个服务都有自己的数据库。

如果这里的讨论让你对迄今为止安全执行的所有交易感到困惑,那么你并不孤单。交易挑战的结果无论如何都不小,值得重点关注。稍后我们将详细介绍这一点。

在本节中,我们讨论了一个数据库,我们称之为应用的主存储。我们还可以将单个数据库分解成每个服务可以使用的小数据库,因此我们可以说每个服务有一个数据库。

在我们更进一步之前,数据库中还有另一部分成为无人区。这是主数据,或者说是静态数据,有些人可能会这样称呼它。

主数据

处理主数据取决于您的个人选择和系统特定要求。如果您看到主数据在很长一段时间内都不会改变,并且占据了少量的记录,那么您最好使用配置文件甚至代码枚举。

这需要有人在发生更改时偶尔推出配置文件。然而,这在未来仍然留下了空白。由于系统的其余部分将依赖于这一个模块,它将负责这些更新。如果该模块运行不正常,依赖它的系统的其他部分也会受到负面影响。

另一种选择是将主数据打包到一个单独的服务中。通过服务交付主数据将提供这样的优势,即服务可以立即了解变化,并了解使用它的能力。

需要时,请求此服务的过程可能与读取配置文件的过程没有太大区别。它可能会慢一些,但是只需要做必要的次数。

此外,您还可以支持不同的主数据集。维护每年都不同的产品集是相当容易的。有了微服务架构风格,将来独立于任何类型的外部依赖总是一个好主意。

在本节中,我们讨论了主数据。当我们与数据交互时,事务起着重要的作用,所以在下一节中,我们将讨论它们。

处理

随着外键的消失以及数据库被分成更小的部分,我们需要设计自己的机制来处理数据完整性。在这里,我们需要考虑到并非所有服务都将在其各自的数据存储范围内成功完成事务的可能性。

一个很好的例子是用户订购特定的产品。接受订单时,有足够的数量可供订购。但是,在记录订单时,由于某种原因,产品服务无法记录订单。我们还不知道是因为数量不足还是系统内部的其他通信故障。这里有两种可能的选择。我们一个一个来讨论。

第一种选择是再试一次,并在以后某个时间执行事务的剩余部分。这将要求我们以跨服务跟踪单个事务的方式编排整个事务。因此,必须跟踪导致为多个服务执行事务的每个事务。如果其中一个没有通过,就应该重试。这可能适用于长期运营。

但是,对于其他操作,这可能会导致真正的问题。如果操作持续时间不长,而您仍然决定重试,结果将导致锁定其他事务或使事务等待,这意味着不可能完成它。

另一种选择是取消分散在各种服务中的整个事务集。这意味着在整个交易集的任何阶段的单个失败将导致所有先前交易的逆转。

这是一个需要最大限度谨慎的领域,是时候好好投资了。只有当事务在任何微服务风格的架构应用中被很好地规划时,才能保证稳定的结果。

当我们处理数据库操作时,事务非常重要;这些操作可以是提取、插入等等。在事务的帮助下,如果任何操作失败,我们可以回滚事务范围内的流程中的完整操作。

现在,让我们继续了解微服务之间的通信。

微服务之间的通信

在前一节中,我们将Order模块分为订单服务,并讨论了如何分解ORDER和PRODUCT表之间的外键关系。

在一个单一的应用中,我们有一个单一的存储库,它查询数据库以从ORDER和PRODUCT表中获取记录。然而,在我们即将推出的微服务应用中,我们将在订单服务和产品服务之间隔离存储库。每个服务都有各自的数据库,每个服务只能访问自己的数据库。订单服务只能访问订单数据库,而产品服务只能访问产品数据库。订单服务不允许访问产品数据库,反之亦然。

We will discuss communication between microservices in Chapter 3, Effective Communication between Services, in detail.

下图显示了使用应用编程接口网关与不同服务的交互:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/2062aad1-f9c1-4c99-976b-ab1720636ff5.png

在上图中,我们可以看到我们的 UI 正在通过 API 网关与订单服务和产品服务进行交互。这两种服务在物理上是相互分离的,并且这些服务之间没有直接的交互。以这种方式执行的通信也被称为基于应用编程接口网关模式的通信。

应用编程接口网关只是一个中间层,用户界面可以通过它与微服务交互。它还提供了一个更简单的界面,并简化了使用这些服务的过程。它根据需要为不同的客户端(浏览器和桌面)提供不同的粒度级别。

我们可以说,它向移动客户端提供粗粒度的 API,向桌面客户端提供细粒度的 API,并且它可以在引擎盖下使用高性能网络,以提供一些重要的吞吐量。

我们可以很容易地将粒度定义为(另请参见https://software engineering . stackexchange . com/questions/385313/什么是粒度):

“…a system is broken down into small parts; large systems can further be broken or torn down to finer parts.”

应用编程接口网关对微服务的好处

毫无疑问,API 网关对微服务是有益的。使用应用编程接口网关,您可以执行以下操作:

  • 通过应用编程接口网关调用服务
  • 减少客户端和应用之间的往返行程
  • 客户端可以在一个地方访问不同的应用编程接口,由网关隔离

它为客户提供了灵活性,使他们可以在需要时与不同的服务进行交互。这样,根本不需要公开完整的/所有的服务。应用编程接口网关是完整应用编程接口管理的一个组成部分。在我们的解决方案中,我们将使用 Azure API Management,我们将在第 3 章、服务之间的有效通信中进一步解释。

应用编程接口网关与应用编程接口管理

在前一节中,我们讨论了应用编程接口网关如何对其客户端隐藏实际的应用编程接口,然后简单地将调用从这些客户端重定向到实际的应用编程接口。应用编程接口管理解决方案提供了一个完整的管理系统来管理其外部消费者的所有应用编程接口。所有的应用编程接口管理解决方案,如 Azure 应用编程接口管理(https://docs.microsoft.com/en-us/azure/api-management/)都提供了各种功能,例如:

  • 设计
  • 发展
  • 安全
  • 出版
  • 可量测性
  • 监视
  • 分析
  • 货币铸造

接下来,让我们重温 FlixOne 案例研究,以便更好地理解。

重新审视 FlixOne 案例研究

在前一章中,我们看了一个虚构公司 FlixOne Inc .的例子,该公司在电子商务领域运营,并拥有自己的.NET 单片应用:FlixOne 书店。

我们已经讨论了以下内容:

  • 如何隔离代码
  • 如何隔离数据库
  • 如何反规范化数据库
  • 如何开始过渡
  • 可用的重构方法

前面几点很重要,因为我们正在将我们的整体应用转换为基于微服务的应用。在第 1 章微服务简介中,我们已经讨论了为什么我们想要构建基于微服务的应用。在现有的电子商务市场中,需要应用的需求、频繁的更新和 100%的正常运行时间(应用的可用性)。

在接下来的部分中,我们将开始将. NET 整体转换为微服务应用。

先决条件

我们将使用以下工具和技术,同时将我们的整体应用转变为微服务风格的体系结构:

  • Visual Studio 2019 或更高版本
  • C# 8.0
  • ASP.NET Core MVC/网络应用编程接口
  • 实体框架核心
  • SQL Server 2008 R2 或更高版本

过渡到我们的产品服务

我们已经有了我们的产品模块。我们现在将收回这个模块,从一个新的 ASP.NET Core MVC 项目开始。为此,请遵循我们在前面章节和第 1 章、微服务简介中讨论的所有步骤。让我们检查一下我们将使用的技术和数据库:

  • 技术栈:我们的产品服务已经选择了这个;我们将和 ASP.NET 芯、c#实体框架 ( EF )等一起走。微服务可以使用不同的技术堆栈编写,并且可以由不同技术创建的客户端使用。对于我们的产品服务,我们将选择 ASP.NET 芯。
  • 数据库:我们已经在第 1 章、微服务介绍中讨论过这个问题,当时我们讨论了一个单一应用和分离它的数据库。在这里,我们将使用 SQL Server,数据库模式将是Product而不是dbo。

我们的产品数据库是独立的。我们将在产品服务中使用该数据库,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/cc42a6c3-26f3-45d8-a4bb-18f7ef9fab6b.png

我们为我们的产品服务创建了一个独立的产品数据库。我们没有迁移整个数据集。在接下来的部分中,我们还将讨论产品数据库迁移。

迁移很重要,因为我们有许多 FlixOne 书店客户的现有记录。我们不能忽略这些记录,它们需要迁移到我们修改过的结构中。我们开始吧。

迁移

在前面的部分中,我们分离了我们的产品数据库,以确保它只被我们的产品服务使用。我们还选择了我们选择的技术堆栈来构建我们的微服务(产品服务)。

在本节中,我们将讨论如何迁移我们现有的代码和数据库,以确保它们适合我们的新架构风格。

代码迁移

代码迁移不仅仅涉及从现有的单一应用中拉出几层代码,然后将其与我们新创建的产品服务捆绑在一起。为了实现这一点,你需要实现你所学到的一切,直到现在。在现有的单片应用中,我们有一个单一的存储库,它对所有模块都是通用的。但是,对于微服务,我们将分别为每个模块创建存储库,并保持它们彼此隔离:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/362c82ed-571f-40fe-a60b-a9050b6dbeac.png

在上图中,产品服务有一个产品存储库,它进一步与其指定的数据存储交互,命名为产品数据库。我们现在将进一步讨论微组件。它们只不过是应用(微服务)的独立部分,即公共类和业务功能。这里值得注意的是产品存储库本身是微服务世界中的一个微组件。

在我们的最终产品服务中,这将在 ASP.NET Core 完成,我们将与模型和控制器一起创建我们的 RESTful 应用编程接口。让我们简单描述一下这两者:

  • 模型:这是一个表示产品服务中数据的对象。在我们的例子中,识别的模型被堆叠到产品和类别字段中。在我们的代码中,模型只不过是一组简单的 C#类。当我们用英孚核心来说话时,它们通常被称为普通旧 CLR 对象 ( POCOs )。POCOs 只不过是没有任何数据访问功能的简单实体。
  • 控制器:这是一个简单的 C#类,继承了Microsoft.AspNetCore.Mvc命名空间的抽象类控制器。它处理 HTTP 请求,并负责创建要发回的 HTTP 响应。在我们的产品服务中,我们有一个产品控制器来处理一切。

让我们按照一步一步的方法来创建我们的产品服务。

创建我们的项目

正如前面几节已经决定的,我们将使用 Visual Studio 在 ASP.NET Core 3.0 或 C# 8.0 中创建ProductService。让我们看看要做到这一点需要哪些步骤:

  1. 启动 Visual Studio。
  2. 通过导航到文件|新建|项目来创建新项目。
  3. 从可用的模板选项中,选择 ASP.NET Core 网络应用,然后单击下一步。以下屏幕截图显示了“创建新项目”窗口:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/5921a5fd-760f-4416-b4eb-3377afe6d929.png

  1. 输入项目名称为FlixOne.BookStore.ProductService,点击确定。

  2. 从模板屏幕中,选择网络应用(模型-视图-控制器),并确保您已选择.NET Core 和 ASP.NET Core 3.1,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/a20bee1f-ea8f-4116-baee-9d708e8b062d.png

  1. 将其余选项保留为默认选项,然后单击创建。新的解决方案应该如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/690f0a5f-83f5-4fb4-a2c6-bd1e9fdae9fb.png

  1. 在解决方案资源管理器中,右键单击(或按下 Alt + 进入)项目,然后单击属性。
  2. 在属性窗口中,单击构建|高级。语言版本是根据框架版本自动选择的。我们的框架是.NET Core 3.0,所以我们的语言应该是 C# 8.0,如下图截图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/31341386-091f-4595-b364-b744ba4b6731.png

Please make sure you select the latest version of C# 8.0. The use of older versions might be troublesome.

添加模型

在我们的单片应用中,我们还没有任何模型类。因此,让我们按照要求添加一个新模型。

要添加新模型,如果项目中不存在,则添加一个新文件夹并命名为Models。在解决方案资源管理器中,右键单击项目,然后单击添加|新建文件夹:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/9c479cda-31ac-4311-94b9-926c41a86276.png

将所有模型类放入名为Models的文件夹中没有硬性规定。事实上,我们可以将模型类放在应用中项目的任何地方。我们遵循这种做法,因为它从文件夹名称中变得不言自明。同时,它很容易识别出这个文件夹是用于模型类的。

要添加新的Product和Category类(这些类将代表我们的概念验证操作),请执行以下操作:

  1. 右键点击Models文件夹,选择【添加|新项目|类】,命名为Product;重复此步骤,添加另一个类,并将其命名为Category。

  2. 现在,将描述我们的产品数据库列名的属性分别添加到Product和Category表中。

There is no restriction, regarding having the property name match the table column name. It is just a general practice.

下面的代码片段描述了我们的Product模型类的样子:

using System;
namespace FlixOne.BookStore.ProductService.Models
{
    public class Product
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string Image { get; set; }
        public decimal Price { get; set; }
        public Guid CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
}

前面的代码示例表示一个产品模型,它包含以下内容:

  • Id是全球唯一标识符 ( GUID )并且代表记录 ID。
  • Name是字符串类型的属性,保存产品名称。
  • Description是一个字符串类型的属性,保存了产品的完整描述。
  • Image是一个字符串类型的属性,包含一个 Base64 字符串。
  • Price是十进制类型的属性,保存产品的价格。
  • CategoryId为 GUID 它保存产品类别的记录标识。
  • Category为虚拟财产,包含产品类别的完整信息。

下面的代码片段显示了我们的Category.cs模型类的样子:

using System;
using System.Collections.Generic;
namespace FlixOne.BookStore.ProductService.Models
{
    public class Category
    {
        public Category()
        {
            Products = new List<Product>();
        }
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public IEnumerable<Product> Products { get; set; }
    }
}

前面的代码代表了我们的类别模型,它包含以下内容:

  • Id是一个 GUID,代表一个记录 ID。
  • Name是字符串类型属性,保存类别名称。
  • Description是一个字符串类型属性,保存了类别的完整描述。
  • Products是属于当前记录类别的所有产品的集合。

让我们在代码示例中添加一个存储库,这样我们就可以进行一些实际的数据库调用。

添加存储库

在我们的整体应用中,我们在整个项目中都有一个公共的存储库。在ProductService中,通过遵循到目前为止所学的所有原则,我们将创建微组件,这意味着封装数据层的独立存储库。

A repository is nothing but a simple C# class that contains the logic to retrieve data from the database and map it to the model.

添加存储库就像执行以下步骤一样简单:

  1. 新建一个文件夹,然后命名为Persistence。
  2. 添加IProductRepository接口和一个将实现IProductRepository接口的ProductRepository类。
  3. 同样,我们命名文件夹Persistence是为了遵循易于识别的一般原则。

以下代码片段概述了IProductRepository界面:

using System;
using System.Collections.Generic;
using FlixOne.BookStore.ProductService.Models;

namespace FlixOne.BookStore.ProductService.Persistence
{
  public interface IProductRepository
 {
 void Add(Product product);
 IEnumerable<Product> GetAll();
 Product GetBy(Guid id);
 void Remove(Guid id);
    void Update(Product product);
  }
}

我们的IProductRepository界面有所有需要的方法:

  • Add:这个方法负责添加一个新产品。
  • GetAll获取所有产品记录,返回产品集合。
  • GetBy根据给定的产品标识获取一个产品。
  • Remove删除特定记录。
  • Update负责更新现有记录。

下一个代码片段提供了ProductRepository类的概述(它仍然没有任何实现,并且还没有与数据库进行任何交互):

using FlixOne.BookStore.ProductService.Contexts;
using FlixOne.BookStore.ProductService.Models;
using Microsoft.EntityFrameworkCore;

 namespace FlixOne.BookStore.ProductService.Persistence
 {
 public class ProductRepository : IProductRepository
 {
 public void Add(Product Product)
 {
 throw new NotImplementedException();
 }
 public IEnumerable<Product> GetAll()
 {
       throw new NotImplementedException();
     }
     public Product GetBy(Guid id)
     {
       throw new NotImplementedException();
     }
 public void Remove(Guid id)
     {
       throw new NotImplementedException();
     }
     public void Update(Product Product)
     {
       throw new NotImplementedException();
     }
   }
 }

前面的代码实现了IProductRepository接口。该代码示例用于演示目的,因此我们没有向实现的方法添加定义。

接下来,让我们看看如何使用Startup.cs的ConfigureServices注册我们的存储库。

注册存储库

对于ProductService,我们将使用内置的依赖注入支持与 ASP.NET Core。为此,请遵循以下简单步骤:

  1. 打开Startup.cs。
  2. 将存储库添加到ConfigureServices方法中。应该是这样的:
using FlixOne.BookStore.ProductService.Persistence;
public void ConfigureServices(IServiceCollection services)
{
 // Add framework services.

  services.AddSingleton<IProductRepository, 
  ProductRepository>();
}

在前面的代码中,我们将我们的存储库注册为由提供的控制反转 ( IoC )的单一服务.NET Core 框架。

在接下来的部分中,我们将讨论我们的产品控制器,并了解如何在代码中添加控制器。

添加产品控制器

最后,我们已经到了可以继续添加控制器类的阶段。该控制器将负责用适用的 HTTP 响应来响应传入的 HTTP 请求。如果你想知道该怎么做,你可以看到HomeController类,因为它是 ASP.NET Core 模板提供的默认类。

右键点击controllers文件夹,选择添加|新建项目选项,选择 API 控制器类。命名为ProductController。在这里,我们将利用单片应用中的任何代码和功能。回到遗留代码,看看您在那里执行的操作;你可以借它们来上我们的ProductController课。参考以下截图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/3a45ea85-a41a-4ebd-b719-133a6aaef847.png

在我们对ProductController进行了所需的修改之后,它应该看起来类似于这样:

 using Microsoft.AspNetCore.Mvc;
 using FlixOne.BookStore.ProductService.Persistence;
 namespace FlixOne.BookStore.ProductService.Controllers
 {
   [Route("api/[controller]")]
   public class ProductController : Controller
   {
     private readonly IProductRepository _ProductRepository;
     public ProductController(IProductRepository ProductRepository)
     {
       _ProductRepository = ProductRepository;
     }
   }
 }

在前一节中,我们为 IoC 注册了ProductRepository,在ProductController中,我们正在使用存储库。在前面的代码示例中,我们使用了构造函数注入,并且使用了带有IProductRepository类型参数的参数化构造函数。

产品服务应用编程接口

在我们的单片应用中,对于Product模块,我们正在执行以下操作:

  • 添加新的Product模块
  • 更新现有的Product模块
  • 删除现有的Product模块
  • 检索Product模块

现在,我们将创建ProductService;我们需要以下 API:

| 原料药资源 | 描述 |
| GET / api / Product | 获取产品列表 |
| GET / api / Product / {id} | 获取产品 |
| PUT / api / Product / {id} | 更新现有产品 |
| DELETE / api / Product / {id} | 删除现有产品 |
| POST / api / Product | 添加新产品 |

接下来,我们将看看如何为这些添加 EF Core 支持。

添加英孚核心支持

在进一步之前,我们需要添加 EF Core 支持,这样我们的服务就可以与产品数据库进行交互。到目前为止,我们还没有向存储库中添加任何可以与数据库交互的方法。要添加 EF Core 支持,我们需要添加 EF Core 的sqlserver包(我们正在添加sqlserver包,因为我们使用 SQL Server 作为我们的 DB 服务器)。打开获取包管理器(工具|获取包管理器|管理获取包)。

打开 NuGet 包,搜索Microsoft.EntityFrameworkCore.SqlServer:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/79fc6c05-2542-4d95-97ed-01c932cb3ed5.png

前面的截图显示了Microsoft.EntityFrameworkCore.SqlServer的搜索结果作为输入。

EF Core DbContext

在前一节中,我们添加了支持 SQL Server 的 EF Core 3.1 包;现在我们需要创建一个上下文,这样我们的模型就可以与我们的产品数据库交互。我们有Product和Category两种型号,你可以参考以下步骤:

  1. 添加新文件夹,然后命名为Contexts—不强制添加新文件夹。

  2. 在Contexts文件夹中,添加一个新的 C#类并命名为ProductContext。我们正在为ProductDatabase创建DbContext,所以为了使它在这里相似,我们创建ProductContext。

  3. 确保ProductContext类继承了DbContext类。

  4. 进行更改,我们的ProductContext类将如下所示:

 using FlixOne.BookStore.ProductService.Models;
 using Microsoft.EntityFrameworkCore;
 namespace FlixOne.BookStore.ProductService.Contexts
 {
   public class ProductContext : DbContext
   {
     public ProductContext(DbContextOptions<
     ProductContext>options): base(options)
     { }
     public ProductContext()
     { }
     public DbSet<Product> Products { get; set; }
     public DbSet<Category> Categories { get; set; }
   }
 }

我们已经创建了上下文,但是这个上下文独立于产品数据库。我们需要添加一个提供者和连接字符串,这样ProductContext就可以和我们的数据库对话了。

  1. 再一次,打开Startup.cs文件,在ConfigureServcies方法下,为我们的英孚核心支持添加SQL Server db提供商。一旦添加了提供商的ConfigureServcies方法,我们的Startup.cs文件将如下所示:
 public void ConfigureServices(IServiceCollection services)
 {
   // Add framework services.

   services.AddSingleton<IProductRepository, ProductRepository>();
   services.AddDbContext<ProductContext>(o =>o.UseSqlServer
   (Configuration.GetConnectionString("ProductsConnection" )));
 }
  1. 打开appsettings.json文件,然后添加需要的数据库连接字符串。在我们的提供商中,我们已经将连接密钥设置为ProductConnection。现在,添加以下代码以设置具有相同键的连接字符串(将Data Source更改为您的数据源):
 {
   "ConnectionStrings": 
   {
     "ProductConnection":
     "Data Source=.SQLEXPRESS;Initial Catalog=ProductsDB;
     IntegratedSecurity=True;MultipleActiveResultSets=True"
   }
 }

前面的代码包含连接我们的应用和数据库的连接字符串。

英孚核心迁移

虽然我们已经创建了产品数据库,但我们不应低估 EF Core 迁移的力量。EF Core 迁移将有助于我们对数据库执行任何未来的修改。这种修改的形式可以是简单的字段添加或对数据库结构的任何其他更新。我们每次都可以简单地依靠这些 EF Core 迁移命令来为我们进行必要的更改。要利用这一功能,请执行以下简单步骤:

  1. 转到工具|否获取包管理器|包管理器控制台。
  2. 从包管理器控制台运行以下命令:
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.Design 
  1. 要启动迁移,请运行以下命令:
 Add-Migration ProductDB

需要注意的是,这只能在第一次执行(当我们还没有通过这个命令创建数据库时)。

  1. 现在,只要您的模型有任何变化,只需执行以下命令:
Update-Database

从前面的命令中,我们可以更新数据库。

至此,我们完成了我们的ProductDatabase创造。现在,是时候迁移我们现有的数据库了。

数据库迁移

从旧数据库迁移到现有数据库有许多不同的方法。我们的整体应用目前有一个巨大的数据库,也包含大量的记录。简单地使用数据库 SQL 脚本是不可能迁移它们的。

我们需要显式创建一个脚本来迁移数据库及其所有数据。另一种选择是根据需要创建数据库包。根据数据和记录的复杂性,您可能需要创建多个数据包,以确保数据正确迁移到我们新创建的数据库ProductDB。

在本节中,我们学习了数据迁移,并在我们想象的应用中找到了数据迁移的范围。一般来说,SQL 脚本足以进行数据库(模式和数据)迁移。但是如果数据库很大,即使我们已经将它分解成每个服务的小数据库,我们在进行数据库迁移时也需要采取更多的预防措施。

在下一节中,我们将重新访问应用存储库和控制器,看看我们的应用如何与数据库交互。

重访存储库和控制器

我们现在准备通过新创建的存储库来促进模型和数据库之间的交互。对ProductRepository进行适当的修改后,会是这样的:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using FlixOne.BookStore.ProductService.Contexts;
using FlixOne.BookStore.ProductService.Models;
namespace FlixOne.BookStore.ProductService.Persistence
 {
   public class ProductRepository : IProductRepository
   {
     private readonly ProductContext _context;
     public ProductRepository(ProductContext context)
     {
       _context = context;
     }
     public void Add(Product Product)
     {
       _context.Add(Product);
       _context.SaveChanges();
     }
     public IEnumerable<Product> GetAll() =>
     _context.Products.Include(c => c.Category).ToList();
     ...
   }
 }

在前面的代码中,我们使用构造函数注入来初始化_context字段。此外,Add方法将新产品插入我们的数据库。类似地,GetAll方法返回我们数据库中所有可用产品的集合。

在这一节中,我们回顾了想象中的应用的代码,并讨论了使用存储库从应用获取、添加和执行其他操作到数据库的流程。

在我们的应用中,模型代表我们的数据库表,视图模型是为视图提供输出的模型。在下一节中,我们将讨论这个视图模型。

引入视图模型

在models文件夹中添加一个新的类并命名为ProductViewModel。我们这样做是因为,在我们的单片应用中,每当我们搜索一个产品时,它都应该显示在其产品类别中。

A ViewModel contains the various properties to hold/represent the data. This data is displayed on our View of the application. ViewModel doesn’t need to have all read/write properties. This data is meant to be shown to the end user on the UI page. This is not a domain model; in our case, we have ProductViewModel as our ViewModel and Product as our domain model.

为了支持这一点,我们需要将必要的字段合并到视图模型中。我们的ProductViewModel课会是这样的:

 using System;
 namespace FlixOne.BookStore.ProductService.Models
 {
   public class ProductViewModel
   {
     public Guid ProductId { get; set; }
     public string ProductName { get; set; }
     public string ProductDescription { get; set; }
     public string ProductImage { get; set; }
     public decimal ProductPrice { get; set; }
     public Guid CategoryId { get; set; }
     public string CategoryName { get; set; }
     public string CategoryDescription { get; set; }
   }
 }

从前面的代码中,我们看到ProductViewModel由以下内容组成:

  • ProductId包含产品的 GUID。
  • ProductName包含产品名称。
  • ProductImage包含产品形象。
  • ProductPrice包含产品价格。
  • CategoryId代表当前产品类别的 GUID。
  • CategoryName代表类别名称。
  • CategoryDescription给出了类别的完整描述。

我们已经看到了我们的ProductViewModel是如何组成的;这是一个与视图绑定并在屏幕上为最终用户生成结果的工具。

我们的ProductViewModel进入画面并获取值,同时我们的控制器的动作方法正在被使用。在下一节中,我们将讨论ProductController。

重温产品控制器

最后,我们准备为ProductService创建一个 RESTful API。更改完成后,ProductController如下图:

 using System.Linq;
 using FlixOne.BookStore.ProductService.Models;
 using FlixOne.BookStore.ProductService.Persistence;
 using Microsoft.AspNetCore.Mvc;
 namespace FlixOne.BookStore.ProductService.Controllers
 {
   [Route("api/[controller]")]
   public class ProductController : Controller
   {
     private readonly IProductRepository _productRepository;
     public ProductController(IProductRepository 
     productRepository) => _productRepository = productRepository;

    [HttpGet]
    [Route("productlist")]
    public IActionResult GetList() => new
    OkObjectResult(_productRepository.GetAll().
    Select(ToProductvm).ToList());

    [HttpGet]
    [Route("product/{productid}")]
    public IActionResult Get(string productId)
 {
 var productModel = _productRepository.GetBy(new Guid(productId));
      return new OkObjectResult(ToProductvm(productModel));
    }

     ...
   }
 }

我们已经完成了创建网络应用编程接口所需的所有任务。现在,我们需要调整一些东西,以便客户端可以获得关于我们的 web APIs 的信息。因此,在接下来的部分中,我们将把 Swagger 支持添加到我们的 web API 文档中。

添加斯瓦格支持

我们在我们的应用编程接口文档中使用了斯瓦格。我们不会在这里深入讨论斯瓦格的细节,因为这超出了本书的范围。Swagger 是一个工具,建立在 OpenAPI 规范之上,它帮助我们轻松地记录我们的 API。在斯瓦格的帮助下,我们可以轻松地为各种应用编程接口/服务创建文档。这些文档对于将要使用这些 API 的最终用户非常有用。

Swagger is a famous open source library that provides documentation for web APIs. Refer to the official link, https://swagger.io/, for more information.

使用斯瓦格添加文档非常容易。请遵循以下步骤:

  1. 打开“获取包管理器”。
  2. 搜索Swashbuckle.AspNetCore包。
  3. 选择软件包,然后安装软件包:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/0d0cc716-5b79-4b57-aa96-9e4be568b50a.png

  1. 它将安装以下内容:
    • Swashbuckle。AspNetCore
    • Swashbuckle。AspNetCore . Swagger
    • Swashbuckle。AspNetCore.SwaggerGen
    • Swashbuckle。AspNetCore.SwaggerUI

这显示在下面的截图中:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/b8645ff7-38dd-4373-b9d7-73f41091c4ad.png

  1. 打开Startup.cs文件,转到 ConfigureServices 方法,添加以下几行来注册 Swagger 生成器:
//Register Swagger
services.AddSwaggerGen(swagger =>
{
    swagger.SwaggerDoc("v1", new Info { Title = "Product APIs", Version = "v1" });
});
  1. 接下来,在 Configure 方法中,添加以下代码:
app.UseSwagger();

app.UseSwaggerUI(option =>
{
 option.SwaggerEndpoint("/swagger/v1/swagger.json", "Product API V1");
});
  1. 按 F5 ,然后运行应用;你会得到一个默认页面。
  2. 通过在网址中添加swagger打开斯瓦格文档。所以,网址是http://localhost:44338/swagger/:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/7c2eed9c-4eba-4de0-892e-168a514bd521.png

前面的截图显示了产品应用编程接口资源,您可以在 Swagger 文档页面中尝试这些应用编程接口。

最后,我们完成了整体的过渡.NET 应用到微服务中,我们讨论了ProductService的逐步过渡。这个应用还有更多步骤:

  • 微服务如何通信:这将在第三章服务之间的有效通信中讨论。

  • 如何测试微服务:这将在第 4 章、用微软单元测试框架测试微服务中讨论。

  • 部署微服务:这将在第 5 章、使用 Docker 部署微服务中讨论。

  • 我们如何确保我们的微服务是安全的,以及我们如何监控我们的微服务:这将在第 6 章、使用 Azure 活动目录和第 7 章、监控微服务中讨论。

  • 如何扩展微服务:这将在第 8 章、用 Azure 扩展微服务中讨论。

在斯瓦格的帮助下,我们展示了我们产品服务的全部应用编程接口,并讨论了应用编程接口文档。

这整个部分为我们提供了一个完整的工作产品应用编程接口,从它的基础到文档。有了这些,我们重新审视了我们想象中的应用的整体。我们已经看到了我们的应用如何与数据库交互,以及我们的用户界面如何提供各种值。在这一部分,我们还创建了我们的产品服务。同样,我们现在可以创建其他所需的服务。为了使我们的代码更简单,我们以产品服务为例,并在本书中对此进行了详细阐述。

摘要

在本章中,我们从高层次讨论了可用于识别和隔离微服务的不同因素。我们还讨论了好服务的各种特征。谈到 DDD,我们了解到它在微服务领域的重要性。

此外,我们详细分析了如何通过各种参数正确实现微服务的垂直隔离。我们借鉴了之前对单片应用及其解决方案在微服务中带来的挑战的理解,并了解到我们可以利用模块相互依赖性、技术利用率和团队结构等因素来识别接缝,并以有组织的方式执行从单片架构到微服务的过渡。

很明显,在这个过程中,数据库会带来明显的挑战。然而,我们通过使用一个简单的策略,确定了我们仍然可以如何执行这个过程,并且我们讨论了可能的方法来做到这一点。然后我们确定,随着外键的减少/移除,事务可以以完全不同的方式处理。

从单块到有限上下文,我们进一步应用我们的知识将 FlixOne 应用转变为微服务架构。

在下一章中,我们将通过涵盖集成模式和 Azure 服务结构来讨论服务之间的通信。

问题

  1. 重构一个整体应用时,我们应该考虑哪些因素?
  2. C# 8.0 的默认接口方法有哪些?
  3. 我们为什么要用斯瓦格?

进一步阅读

三、服务之间的有效通信

在前一章中,我们使用. NET 单片应用开发了微服务。这些服务相互独立,位于不同的服务器上。有什么更好的服务间通信方式,其中一个服务与另一个服务进行交互和通信?在微服务中,每个服务可能相互独立,也可能不相互独立。例如,结帐服务可能需要产品服务,但产品服务可能不需要结帐服务。在这种情况下,服务之间的通信非常重要。我们将详细讨论某些支持服务间通信的模式。

在本章中,我们将讨论有助于我们促进这种交流的各种模式和方法。我们还将介绍使用 Azure 服务结构和 Kubernetes 的集成模式。

在本章中,我们将涵盖以下主题:

  • 了解服务之间的通信
  • 了解集成模式
  • 了解蔚蓝服务结构
  • 具有立方体的微服务
  • 构建微服务应用时考虑物联网

技术要求

本章包含各种代码示例来解释一些概念。代码将很简单,只是为了演示。

要运行和执行代码,您需要以下先决条件:

  • Visual Studio 2019
  • 。网络核心设置和运行

要运行这些代码示例,您需要安装 Visual Studio 2019 或更高版本(我们首选的 IDE)。为此,请遵循以下说明:

  1. 从https://docs . Microsoft . com/en-us/Visual Studio/install/install-Visual Studio下载 Visual Studio 2019(社区免费)。
  2. 按照操作系统的安装说明进行操作。Visual Studio 有多个版本。我们使用的是视窗操作系统。

如果你没有.NET Core 3.1 安装好了,可以去https://www.microsoft.com/net/download/windows下载设置。

The complete source code is available here: https://github.com/PacktPublishing/Hands-On-Microservices-with-C-8-and-.NET-Core-3.0.

让我们开始第一部分,它是关于理解服务之间的通信。

了解服务之间的通信

在. NET 单片应用的情况下,如果需要访问第三方组件或外部服务,我们使用 HTTP 客户端或另一个客户端框架来访问资源。在第二章重构整块中,我们开发了产品服务,使其能够独立工作。但是这对我们的应用没有好处;我们需要一些服务来相互交互。

这是一个挑战—让服务相互通信。产品服务和订单服务都托管在不同的服务器上。这两个服务器彼此独立,都基于 REST,并且都有自己的端点,它们可以从这些端点相互通信。(当一个服务与另一个服务交互时,反之亦然,我们称之为服务间通信)。

服务之间有几种通信方式;让我们简单讨论一下:

  • 同步:在这种情况下,客户端向远程服务(称为服务)请求特定的功能,然后等待直到得到响应:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/522789ad-8311-4975-a9ae-54fa15e35376.png

在上图(图示视图,不完整)中,您可以看到我们不同的微服务相互通信。我们所有的服务都是 RESTful 的。它们基于 ASP.NET Core 网络应用编程接口。在下一节中,我们将讨论如何调用服务。这就是所谓的同步方法,客户端必须等待服务的响应。在这种情况下,客户端必须等到得到完整的响应。

  • 异步:在这种情况下,客户端向远程服务(称为服务)请求特定的功能。客户不会等待,但会关心响应。异步完成分配给他们的任务,这也适用于我们的日常生活。例如,如果我们正在做早餐,那么我们会遵循某些任务;也就是说,我们可以准备我们的茶,煮一个鸡蛋,等等。现在,让我们以想象中的应用为例。在这里,我们试图添加一个产品。用户指示系统这样做,并将数据值传递给控制器。然后,控制器调用存储库,存储库使用实体框架的上下文保存数据。我们将在接下来的章节中详细讨论这一点。

合作方式

在前一节中,我们讨论了两种不同的服务交互模式。这些模式是合作的风格。让我们来看看它们:

  • 请求/响应:这种情况下,客户端发送请求,等待服务器的响应。这是同步通信的一种实现。然而,请求/响应不仅仅是同步通信的实现;我们也可以将它用于异步通信。让我们看一个例子来理解这一点。在 第二章**重构整块中,我们开发了ProductService。该服务包括GetProduct方法,是同步的。每当客户端调用此方法时,都必须等待响应:
[HttpGet]
[Route("GetProduct")]
public IActionResult Get() => 
return new
OkObjectResult(_productRepository.GetAll().ToViewModel());

根据前面的代码片段,每当客户端调用这个方法(请求这个方法)时,他们都必须等待响应。换句话说,他们必须等到ToViewModel()扩展方法被执行:

[HttpGet]
[Route("GetProductSync")]
public IActionResult GetIsStillSynchronous()
{
   var task = Task.Run(async() => await
   _productRepository.GetAllAsync());
   return new OkObjectResult(task.Result.ToViewModel());
}

在前面的代码片段中,我们可以看到我们的方法是这样实现的,即每当客户端发出请求时,他们都必须等到async方法被执行。在前面的代码中,我们使用async作为sync。看看task.Result.ToViewModel(),它正在返回前面代码中的响应,我们可以看到.Result是我们的代码sync的来源。

为了缩短我们的代码,我们在已经存在的代码中添加了一些扩展方法,我们在第 2 章、重构整体中写道:

namespace FlixOne.BookStore.ProductService.Helpers
{
    public static class Transpose
    {
        public static ProductViewModel ToViewModel(
                               this Product product)
        {
            return new ProductViewModel
            {
                CategoryId = product.CategoryId,
                CategoryDescription = product.Category.Description,
                CategoryName = product.Category.Name,
                ProductDescription = product.Description,
                ProductId = product.Id,
                ProductImage = product.Image,
                ProductName = product.Name,
                ProductPrice = product.Price
            };
        }

        public static IEnumerable<ProductViewModel> ToViewModel(this IEnumerable<Product> products) => products.Select(ToViewModel).ToList();
    }
}

总之,我们可以说请求/响应的协作风格并不意味着它只能同步实现;我们也可以为此使用异步调用。

  • 基于事件的:这种协作风格的实现纯粹是异步的。这是一种实现方法,发出事件的客户端不知道如何反应。在前一节中,我们以同步的方式讨论了ProductService。让我们看一个用户/客户如何下单的例子。以下流程图是购书功能的图示概述:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/6e59730b-3845-4b41-9b11-9bd40ab31c25.png

上图显示了以下内容:

从概念上讲,这看起来很容易;然而,当我们讨论实现微服务时,我们讨论的是单独托管的服务,它们有自己的 REST API、数据库等。这现在听起来更复杂了。涉及许多方面,例如,一个服务如何在一个或多个服务成功响应时调用另一个服务。下图显示了这种事件驱动架构的样子:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/159e83ca-fd32-4809-944a-44f1fb9d2f80.png

在上图中,我们可以看到订单服务执行时发票服务和产品服务被触发。这些服务调用其他内部异步方法来完成它们的功能。

We are using Azure API management as our API gateway. In the upcoming sections, we will discuss this in detail.

这一部分是关于服务之间的通信,我们了解了各种通信方式,然后是关于协作的讨论。

在下一节中,我们将讨论如何实现我们的应用所需的各种集成模式。

了解集成模式

到目前为止,我们已经讨论了服务间通信,并通过使用同步和异步通信完成了ProductService的实际实现。我们还实现了微服务,使用不同风格的协作。我们的 FlixOne 书店(按照微服务架构风格开发)需要更多的交互,这意味着它需要更多的模式。在本节中,我们将帮助您理解它们。

The complete application of the FlixOne bookstore can be found in Chapter 11, Building a Microservice Application.

应用编程接口网关

在协作风格部分,我们讨论了两种可以用来促进微服务之间相互通信的风格。我们的应用分为各种微服务:

  • 产品服务
  • 订单服务
  • 发票服务
  • 客户服务

在我们的 FlixOne 书店(用户界面)中,我们需要展示一些细节:

  • 书名、作者姓名、价格、折扣等等
  • 有效
  • 书评
  • 图书评级
  • 出版商排名和其他出版商信息

在我们检查实现之前,让我们讨论一下 API 网关。

应用编程接口网关是所有客户端的单一入口点。它充当客户端应用和服务之间的代理。在我们的例子中,我们使用 Azure API 管理(APIM) 作为我们的 API 网关。

Please refer to the Appendix for more details and implementation of an API gateway and the related BFF pattern.

应用编程接口网关负责以下功能:

  • 接受应用编程接口调用并将它们路由到我们的后端
  • 验证应用编程接口密钥、JWT 令牌和证书
  • 通过 Azure AD 和 OAuth 2.0 访问令牌支持身份验证
  • 强制实施使用配额和费率限制
  • 在不修改代码的情况下,动态转换我们的应用编程接口
  • 缓存后端响应,无论它们设置在哪里
  • 出于分析目的记录呼叫元数据

Refer to Azure API Management (https://docs.microsoft.com/en-us/azure/api-management/) to find out more about the process of setting up the API Azure portal and working with REST APIs.

下图显示了作为应用编程接口网关的 Azure 应用编程接口管理:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/6ee975e0-fc79-44f2-804b-1e4977f6964a.png

在前面的流程图中,我们有不同的客户端,如移动应用、桌面应用和网络应用,它们使用微服务。

我们的客户不知道在哪个服务器上可以找到我们的服务。应用编程接口网关提供自己服务器的地址,并使用有效的Ocp-Apim-Subscription-Key在内部验证来自客户端的请求。

Additional steps must be taken to force traffic through APIM. With the respective URL, it is possible to bypass this by going directly to the backend, if it is exposed externally.

我们的ProductService有一个 REST API。它包含以下资源:

| API 资源 | 描述 |
| GET /api/product | 获取产品列表 |
| GET /api/product/{id} | 获取产品 |
| PUT /api/product/{id} | 更新现有产品 |
| DELETE /api/product/{id} | 删除现有产品 |
| POST /api/product | 添加新产品 |

我们已经创建了ProductClient,这是一个. NET 控制台应用。它通过提供订阅密钥向 Azure 应用编程接口管理发出请求。下面是这个的代码片段:

namespace FlixOne.BookStore.ProductClient
{
   class Program
   {
      private const string ApiKey = "myAPI Key";
      private const string BaseUrl = "http://localhost:3097/api";
      static void Main(string[] args)
      {
         GetProductList("/product/GetProductAsync");
         //Console.WriteLine("Hit ENTER to exit...");
         Console.ReadLine();
      }
      private static async void GetProductList(string resource)
      {
         using (var client = new HttpClient())
         {
            var queryString =
            HttpUtility.ParseQueryString(string.Empty);

            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-
            Key", ApiKey);

            var uri = $"{BaseUrl}{resource}?{queryString}";

            //Get asynchronous response for further usage
            var response = await client.GetAsync(uri);
            Console.WriteLine(response);
          }
       }
    }
 }

在前面的代码中,我们的客户端请求一个 REST API 来获取所有的产品。下面是代码中出现的术语的简要描述:

| BaseUrl | 这是代理服务器的地址。 |
| Ocp-Apim-Subscription-Key | 这是应用编程接口管理为客户选择的特定产品分配的密钥。 |
| Resource | 这是我们的应用编程接口资源,通过 Azure 应用编程接口管理进行配置。它将不同于我们实际的 REST API 资源。 |
| Response | 这是指对特定请求的响应。在我们的例子中,这是默认的 JSON 格式。 |

由于我们使用 Azure 应用编程接口管理作为应用编程接口网关,我们将获得某些好处:

  • 我们可以从单个平台管理我们的各种 APIs 例如,ProductService、OrderService等服务可以被很多客户端轻松管理和调用。
  • 因为我们使用的是应用编程接口管理,它不仅为我们提供了代理服务器;它还允许我们为我们的 API 创建和维护文档。
  • 它提供了一个内置的工具,这样我们就可以为配额、输出格式和格式转换定义各种策略,例如从 XML 到 JSON,反之亦然。

因此,在应用编程接口网关的帮助下,我们可以获得一些伟大的功能。

事件驱动模式

微服务架构使用每个服务一个数据库的模式,这意味着它为每个依赖或独立的服务包含一个独立的数据库:

  • 依赖服务:我们的应用需要一些外部服务(第三方服务或组件等)和/或内部服务(这些是我们自己的服务)才能按预期工作或运行。例如,结账服务需要客户服务,还需要外部(第三方)服务来验证客户身份(如印度客户的 Aadhaar 身份证或美国客户的 SSN 身份证)。这里,我们的结账服务是一个依赖服务,因为它需要两个服务(一个内部服务和一个外部服务)才能按预期运行。如果服务依赖的任何或所有服务不能正常工作(服务不能正常工作的原因有很多,包括网络故障、未处理的异常等),依赖服务就不能工作。
  • 独立服务:在我们的应用中,我们有不依赖任何其他服务正常工作的服务。这种服务被称为独立服务,它们可以自我托管。我们的客户服务不需要任何其他服务来正常运行,这意味着它是一个独立的服务,不需要任何其他服务的输入。不过,这并不意味着我们的其他服务不依赖于客户服务;其他服务可能依赖也可能不依赖于此服务。

主要的挑战是维护业务事务,以确保这些服务之间的数据一致性。例如,如下图所示,客户服务要知道结账服务何时以及如何运作,它需要客户服务的功能。一个应用中可能有多个服务(服务可能是自托管的)。在我们的例子中,当结账-服务被触发并且客户服务没有被调用时,那么我们的应用将如何识别客户的详细信息?

ASP.NET WebHooks(https://docs.microsoft.com/en-us/aspnet/webhooks/)也可以用来提供事件通知。

为了克服我们已经讨论过的相关问题和挑战(对于结账-服务和客户服务,我们可以使用事件驱动模式(或最终的一致性方法)并使用分布式事务。

Distributed transactions are where data flows on more than one network. These transactions are capable of updating this data on networked computer systems. These also detect and roll back the subsequent operations, if any fail.

下图描述了事件驱动模式在我们的应用中的实际实现,其中产品-服务订阅事件,事件管理器管理所有事件:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/f0dc5b37-faca-46cf-b96b-86e6286f2e1a.png

在事件驱动模式中,我们以这样一种方式实现服务:每当一个服务更新其数据时,它就发布一个事件,而另一个服务(依赖服务)订阅这个事件。现在,每当一个依赖服务接收到一个事件,它就更新它的数据。这样,如果需要,我们的依赖服务可以获取和更新它们的数据。上图显示了服务如何订阅和发布事件的概述。这里,事件管理器可以是运行在服务上的程序,也可以是帮助您管理订阅者和发布者的所有事件的中介。

它登记发布者的事件,并在特定事件发生/触发时通知订阅者。它还可以帮助您形成队列并等待事件。在我们的实现中,我们将使用 Azure 服务总线队列。

让我们考虑一个例子。在我们的应用中,这是我们的服务发布和接收事件的方式:

  • 客户服务为用户执行一些检查,即登录检查、客户详细信息检查等。进行这些必要的检查后,服务会发布一个名为CustomerVerified的事件。
  • 结账-服务接收该事件,并在执行必要的操作后,发布名为ReadyToCheckout的事件。
  • 订单服务收到此事件并更新数量。
  • 结账一完成,结账服务就会发布一个事件。从外部服务(无论是CheckedoutSuccess还是CheckedoutFailed)收到的任何结果都由结账-服务使用。
  • 当InventoryService收到这些事件时,它会更新数据,以确保该项被添加或删除。

通过使用事件驱动模式,服务可以自动更新数据库并发布事件。

事件源模式

这种模式帮助我们确保每当状态改变时,服务都会发布一个事件。在这种模式中,我们将业务实体(产品、客户等)视为一系列状态变化事件。事件商店保存事件,这些事件可用于订阅或作为其他服务。这种模式简化了我们的任务,因为我们不需要同步数据模型和业务领域。它提高了性能、可扩展性和响应能力。这只是定义了一种方法,通过一系列事件来指示我们如何处理数据上的各种操作。这些事件被记录在商店里。事件表示对数据进行的一组更改,例如InvoiceCreated。

下图描述了事件将如何为 ORDERSERVICE 工作:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/939b6a69-314a-4d21-8b13-b85b1a4334c8.png

上图显示了以下实现:

  1. 命令从用户界面发出一本书进行订购。
  2. 订单服务查询(来自事件商店,并用CreateOrder事件填充结果。
  3. 然后,命令处理程序引发一个事件来订购图书。
  4. 我们的服务执行相关操作。
  5. 最后,系统将事件附加到事件存储中。

最终一致性模式

最终一致性是数据一致性方法的实现。这意味着实现,因此该系统将是一个具有高可用性的可扩展系统。

“The distributed system needs to be specified as having eventual consistency as an explicit requirement. Eventual consistency comes from the systems that exhibit scalability and high availability.”

根据该分布式数据,商店服从一致性、可用性(网络)、分区容差 ( CAP )定理。CAP 定理也被称为布鲁尔定理。根据这个定理,在分布式系统中,我们只能从以下三个中选择两个:

  • 一致性(丙)
  • 可用性(一)
  • 分区容差

以我们想象的系统为例,它是高可用的(A),高度一致的©,并且没有分区(CA)。当我们要求并进行分区§时,我们的系统有多达 n 个分区,或者我们说我们在持续地对我们的系统进行分区。在这种情况下,这是一个非常复杂的场景,数据很难到达或覆盖所有分区。这就是为什么当我们进行分区时,我们要么使系统高度可用(AP),要么使系统高度一致(CP)。

补偿交易

补偿事务允许我们回滚或撤消我们在一系列步骤中执行的所有任务。假设一个或多个服务已经实现了一系列操作,并且其中一个或多个已经失败。我们的下一步会是什么?我们会颠倒所有的步骤,还是承诺一个半完成的功能?

在我们的案例中,客户订购了一本书,ProductService将订购的书标记为暂时售出。订单确认后,OrderService调用外部服务完成支付流程。如果付款失败,我们将需要撤销我们之前的任务,这意味着我们将不得不检查ProductService,以便它将特定的书标记为未售出。

竞争消费者

竞争消费者允许我们为多个并发消费者处理消息,以便他们在同一个通道上接收这些消息。这个应用是用来处理大量请求的。

下图显示了此的实现:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/f2d8ef15-138e-4a14-8316-2b6d2a8eac2c.png

这个消息流是通过将一个消息传递系统传递给另一个服务(消费者服务)来实现的,这个服务可以异步处理。

这个场景可以通过使用 Azure 服务总线队列来实现。我们将在下一节讨论这个问题。

了解蔚蓝服务结构

Azure 服务结构是一个分布式系统的平台;它帮助我们轻松管理可扩展的微服务。它克服了开发人员和基础设施人员面临的各种挑战。Azure Service Fabric 是一个分布式系统平台,它使我们能够轻松地打包、部署和管理可扩展且可靠的微服务和容器。

服务结构体系结构

服务结构是服务的集合。每个集合被分成不同的子系统。每个子系统都有自己特定的职责,允许我们编写以下类型的应用:

  • 可扩展的应用
  • 可管理的应用
  • 可测试的应用

主要子系统构成了服务结构体系结构,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/ee025292-8a42-42d8-85bb-0076119488e2.png

底层的第一层,即传输子系统,负责在服务结构集群中的节点之间提供安全的通信通道。让我们更详细地看一下所有这些子系统:

  • 传输子系统为集群内和集群间的通信提供了通信通道。用于通信的通道由 X509 证书或 Windows 安全保护。该子系统支持单向和请求-响应通信模式。联盟子系统使用这些信道来广播和多播消息。该子系统位于服务结构内部,开发人员不能直接将其用于应用编程。
  • 联盟子系统负责将虚拟机或物理机逻辑分组在一起,形成服务结构集群。该子系统使用传输子系统提供的通信基础设施来实现这种分组。这有助于服务结构更高效地管理资源。该子系统的主要职责包括故障检测、领导选举和路由。子系统在为集群分配的节点上形成环形拓扑。在系统中实现了令牌租用机制和心跳检查,以检测故障、执行领导者选举并实现一致的路由。
  • 托管在平台上的服务的可靠性子系统通过管理集群中跨节点的故障转移、复制和资源平衡来确保。这个子系统中的复制器逻辑负责跨服务的多个实例复制状态。该子系统的主要任务是维护服务部署中主副本和辅助副本之间的一致性。它与故障转移单元和重新配置代理交互,以便了解需要复制什么。
  • 管理子系统处理已经部署在服务结构集群上的工作负载的应用生命周期管理。应用开发人员可以通过管理 API 或 PowerShell cmdlets 访问管理子系统的功能,以调配、部署、升级或取消调配应用。所有这些操作都可以在不停机的情况下执行。管理子系统有三个关键组件:集群管理器、运行状况管理器和映像存储。群集管理器与故障转移管理器交互。同时,可靠性子系统中的资源管理器部署可用节点的应用,同时考虑放置约束。它负责应用的生命周期,从资源调配到取消资源调配。它还与运行状况管理器集成,在服务升级期间执行运行状况检查。顾名思义,运行状况管理器负责监控应用、服务、节点、分区和副本的运行状况。它还负责汇总健康状态并将其存储在集中的健康存储中。API 暴露在这个系统之外,用于查询健康事件,以便它们可以执行纠正措施。这些应用编程接口可以返回特定集群资源的原始事件或聚合健康数据。映像存储负责持久化和分发部署在服务结构集群上的应用二进制文件。
  • 托管子系统负责管理节点范围内的应用部署。集群管理器向托管子系统发出信号,通知它要在特定节点上管理的应用部署。然后,托管子系统管理该节点上应用的生命周期。它与可靠性子系统和管理子系统交互,以确保每个部署的健康。
  • 通信子系统提供服务发现功能,并提供使用命名服务的集群内消息传递功能。命名服务用于在集群中定位服务。它还允许用户安全地与集群中的任何节点通信、检索服务元数据和管理服务属性。命名服务还公开了 API,允许用户解析每个服务的网络位置,尽管它们是动态放置的。
  • 可测试性子系统为开发人员、部署工程师和测试人员提供了一个工具列表,这样他们就可以引入受控故障并运行测试场景,以验证已经部署在服务结构上的服务的状态转换和行为。当一个集群被提供或者当一个故障动作或测试场景已经启动一个命令时,故障分析服务自动启动
    。

在接下来的章节中,我们将更详细地讨论服务结构,在这里我们将详细介绍编排器和编程模型。

讨论管弦乐队

简而言之,编排器是一个用于管理服务部署的自动化软件。该软件包旨在从顶级用户那里抽象出有关配置、部署、故障处理、扩展和优化其管理的应用的复杂性。例如,关联度编排应该准备好使用指定服务实例数量的配置,以运行和执行基于多个高级因素的服务部署任务。这些因素包括群集期间节点上的资源可用性、放置限制等。管弦乐队可以对故障处理和服务恢复收费。如果集群中的一个节点出现故障,安排者必须优雅地处理这个问题,同时保证服务的便捷性。

服务结构集群资源管理器可以是在我们的集群内运行的中央服务。它管理集群内服务的指定状态,以及相关的资源消耗和放置规则。我们将在下一节讨论这个问题。

服务结构编程模型概述

服务结构提供了我们编写和管理服务的多种方式。服务可以选择使用服务结构 API 来充分利用平台的特性和应用框架。服务也可以是以任何语言编写的任何编译过的可执行程序,或者是运行在服务结构集群上的容器中的代码。让我们现在来看看这些:

  • Guest 可执行文件是一个现有的可执行文件,可以用任何语言编写,可以作为服务在应用中运行。这些客户可执行文件没有直接使用服务结构 API。Visual Studio 允许我们在服务结构集群上部署来宾可执行文件。
  • 容器是完整文件系统的一部分,包含工具、运行时和系统库。
  • 可靠的服务是一个轻量级框架,用于编写与 Service Fabric 平台集成并受益于该平台功能的服务。可靠的服务提供了一组最小的 API,允许服务结构运行时管理服务的生命周期,以便它们可以与运行时交互。应用框架很小,因此您可以完全控制设计和实现选择。它也可以用来托管任何其他应用框架,如 ASP.NET Core。
  • ASP.NET Core是一个开源的跨平台框架,我们可以用它来构建网络应用、物联网应用和移动后端。服务结构与 ASP.NET Core 集成,因此我们可以编写无状态和有状态的 ASP.NET Core 应用,利用可靠集合和服务结构的高级编排功能。
  • 可靠参与者构建在可靠服务之上,是一个基于参与者设计模式实现虚拟参与者模式的应用框架。可靠参与者框架使用独立的计算和状态单元,并带有一个称为参与者的单线程执行。可靠参与者框架为参与者提供内置通信,并预设状态持久性和扩展配置。因为可靠参与者是建立在可靠服务上的应用框架,所以它与服务结构平台完全集成,并且受益于该平台提供的全套功能。

本节的目的是讨论服务结构,并提供其编程模型的概述。在下一节中,我们将学习如何实现关于竞争消费者的信息。

实施关于竞争消费者的信息

在竞争消费者部分,我们讨论了竞争消费者是传递信息的一种方式。在本节中,我们将讨论 Azure 服务总线并实现 Azure 消息队列。

蔚蓝服务巴士

在事件驱动模式中,我们讨论了发布和订阅事件。我们使用事件管理器来管理所有事件。在本节中,我们将学习 Azure 服务总线如何管理事件,并提供与微服务一起工作的工具。它像信息传递服务一样工作,并且它使服务之间的通信完美无缺。在我们的例子中,每当服务需要交换信息时,它们将使用该服务进行通信。

Azure 服务总线提供两种主要类型的服务:

  • 代理通信:这项服务也被称为雇佣服务。它的工作原理类似于现实世界中的邮政服务。每当一个人想要发送消息或信息时,他/她可以向另一个人发送一封信。有了邮局,我们可以以信件、包裹、礼物等形式发送各种类型的信息。当我们使用代理通信类型时,我们自己不必费心传递消息,因为它确保我们的消息被传递,即使发送者和接收者都不在线。这是一个消息传递平台,包含队列、主题、订阅等组件。
  • 非代理通讯:这类似于打电话。在这种情况下,呼叫者(发送者)呼叫一个人(接收者),而没有任何指示他/她是否将应答该呼叫的确认。在这里,发送方发送信息,并依靠接收方接收通信,并将消息传递回发送方。请看下图,它展示了 Azure 服务总线:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/1a62aa4b-8b16-443e-ba19-3601bd4e2a28.png

请参见微软 Azure 服务总线的文档。这里有一个描述:

“Service Bus is a cloud service and is shared by multiple users. To get started with it, you need to create a namespace and define the communication mechanism.”

上图是 Azure 服务总线的示意图,描述了四种不同的通信机制。每个人在连接应用时都有自己的喜好:

  • 队列:这些队列就像经纪人一样,允许单向通信。
  • 主题:与队列类似,主题提供单向通信,但单个主题可以有多个订阅。
  • 中继:这些不像队列和话题那样存储任何消息。相反,它们提供双向通信并将消息传递给应用。
  • 通知中心:这将消息从服务器应用分发到平台上的客户端设备。

Azure 消息队列

Azure 队列是使用 Azure 表的云存储帐户。它们允许我们在应用之间对消息进行排队。Azure 队列存储有一组不同于 Azure 服务总线的功能。在接下来的部分中,我们将实现消息队列,这是 Azure 服务总线的一部分。

在本节中,我们已经讨论了 Azure 消息队列,通过遵循 Azure 服务总线,它允许我们收集关于竞争消费者的信息。在下一节中,我们将实现关于服务结构的信息。

在服务结构上实现信息

在本节中,我们将通过创建以下内容来查看 Azure 服务总线队列的实际实现:

  • 服务总线命名空间
  • 服务总线消息队列
  • 向其发送消息的控制台应用
  • 接收消息的控制台应用

先决条件

我们需要以下内容来实施此解决方案:

  • Visual Studio 2019 或更高版本
  • 有效的 Azure 订阅

如果您没有 Azure 套餐,您可以通过在https://azure.microsoft.com/en-us/free/注册免费获得一个。

现在您已经拥有了一切,请按照以下步骤开始:

  1. 登录蔚蓝门户(https://portal.azure.com/)。
  2. 在左侧导航栏中,单击服务总线。如果此选项不可用,您可以通过单击“更多服务”找到它。
  3. 单击添加。这将打开“创建命名空间”对话框:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/403f19c9-b9f6-41e4-94ba-c30f30337c12.png

  1. 在“创建命名空间”对话框中,输入一个命名空间(命名空间应该是全局唯一的),如flixone。接下来选择定价层,即Basic、Standard或Premium。
  2. 选择您的订阅。
  3. 选择现有资源或创建新资源。
  4. 选择要承载命名空间的位置。完成后,单击创建。
  5. 打开一个新创建的命名空间(我们刚刚创建了flixone)。
  6. 现在,单击共享访问策略。
  7. 点击 RootManageSharedAccessKey,如下图截图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/d46565f3-5a63-4c41-814e-dedd9503395f.png

  1. 点击flixone命名空间主对话框中的【队列】,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/ce5d9058-2965-48b9-bfff-b8e37c94b2e7.png

  1. 在“策略:RootManageSharedAccessKey”窗口中,记下主键连接字符串,以便以后使用。

  2. 点击名称添加队列(比如flixonequeue)。然后,单击创建(我们使用 REST 值作为默认值),如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/2bb4457f-c723-4d52-bb0c-322c55cc23a7.png

前面的截图是“创建队列”对话框的截图。在“创建队列”对话框中,我们可以创建队列。例如,在这里,我们正在创建一个名为 flixonequeue 的队列。可以通过访问“队列”对话框来验证队列。

现在,我们准备创建发送方和接收方应用。

向队列发送消息

在本节中,我们将创建一个控制台应用,它将实际向队列发送消息。要创建此应用,请执行以下步骤:

  1. 创建一个新的控制台应用,然后使用 Visual Studio 的新项目(C#)模板将其命名为FlixOne.BookStore.MessageSender:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/5a5d7b92-de78-489b-8fba-1c8805da91d0.png

  1. 右键单击项目,添加 Microsoft Azure 服务总线 NuGet 包。
  2. 使用以下代码将消息发送到队列。您的Program.cs文件将包含以下MainAsync()方法:
private static async Task MainAsync()
{
    const int numberOfMessagesToSend = 10;
    _client = new QueueClient(_connectionString, _queuename);
    WriteLine("Starting...");
    await SendMessagesAsync(numberOfMessagesToSend);
    WriteLine("Ending...");
    WriteLine("Press any key...");
    ReadKey();
    await _client.CloseAsync();
}

在前面的代码中,我们通过提供已经在 Azure 门户中设置的ConnectionString和QueueName来创建队列客户端。这段代码调用SendMessagesAsync()方法,该方法接受一个包含需要发送的消息数的参数。

  1. 创建一个SendMessagesAsync()方法,并添加以下代码:
private static async Task SendMessagesAsync(int numberOfMessagesToSend)
{
    try
    {
        for (var index = 0; index < numberOfMessagesToSend; index++)
        {
            var customMessage = $"#{index}:A message from 
                            FlixOne.BookStore.MessageSender.";
            var message = new Message(Encoding.UTF8.GetBytes(
                                      customMessage));
            WriteLine($"Sending message: {customMessage}");
            await _client.SendAsync(message);
        }
    }
    catch (Exception exception)
    {
        WriteLine($"Weird! It's exception with message:
                   {exception.Message}");
    }
}
  1. 运行程序并等待一段时间。您将获得以下输出:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/43ab471c-3f1a-454e-a0d4-782396aef14a.png

  1. 转到 Azure 门户,然后转到创建的队列,检查它是否显示消息。在下面的截图中,我们可以看到 flixonequeue 的概述,其中我们可以看到活动消息计数和更多信息:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/c1326315-b6c5-45c0-a5b6-963f2901d3ab.png

前面的截图来自 Azure 门户,是 flixonequeue(服务总线)的概览屏幕。目前,我们有 10 条消息(活动消息数)。

添加配置设置

在上一节中,我们使用了ConnectionString和QueueName的常数值。如果我们需要更改这些设置,我们必须对代码进行更改。然而,为什么我们要为这么小的变化进行代码更改呢?为了克服这种情况,我们将使用配置设置。

在本节中,我们将借助Microsoft.Extensions.Configuration命名空间中的IConfigurationRoot来添加配置。

  1. 首先,右键单击项目,然后单击管理 NuGet 包。搜索Microsoft.Extensions.Configuration NuGet 包,如下图截图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/b7d838e1-c11d-4a38-b702-90969239114b.png

  1. 现在,搜索Microsoft.Extensions.Configuration.Json NuGet 包并选择它:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/e588cd1c-7f59-4c87-911c-f6549cf38ec9.png

  1. 将以下ConfigureBuilder()方法添加到Program.cs文件中:
private static IConfigurationRoot ConfigureBuilder()
{
   return new ConfigurationBuilder()
 .SetBasePath(Directory.GetCurrentDirectory())
 .AddJsonFile("appsettings.json")
 .Build();
}
  1. 现在,将appsettings.json文件添加到项目中,包括以下属性:
{
   "connectionstring":
   "Endpoint=sb://flixone.servicebus.windows.net/;
   SharedAccessKeyName=
   RootManageSharedAccessKey;SharedAccessKey=
   BvQQcB5FhNxidcgEhhpuGmi/
   XEqvGho9GmHH4yjsTg4=",
   "QueueName": "flixonequeue"
}
  1. 将以下代码添加到main()方法中:
var builder = ConfigureBuilder();
_connectionString = builder["connectionstring"];
_queuename = builder["queuename"];

通过添加前面的代码,我们可以从.json文件中获取connectionstring和queuename。现在,如果我们需要更改这些字段中的任何一个,我们不需要对代码文件进行更改。

从队列接收消息

在本节中,我们将创建一个控制台应用,它将从队列中接收消息。要创建此应用,请执行以下步骤:

  1. 创建一个新的控制台应用(在 C#中)并命名为FlixOne.BookStore.MessageReceiver。
  2. 添加 Azure 服务总线的 NuGet 包(我们在上一个应用中添加的),包括Microsoft.Extensions.Configuration和Microsoft.Extensions.Configuration.Json。
  3. 使用以下代码从 Azure 总线服务队列接收消息。这里,你的program.cs文件包含了ProcessMessagesAsync()方法:
 static async Task ProcessMessagesAsync(Message message,
 CancellationToken token)
 {
    WriteLine($"Received message: #
    {message.SystemProperties.SequenceNumber}
    Body:{Encoding.UTF8.GetString(message.Body)}");
    await _client.CompleteAsync
    (message.SystemProperties.LockToken);
 }
  1. 运行应用,然后查看结果:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/d1ea81fc-9dfa-4a99-a978-00df7b621661.png

  1. 控制台窗口将显示消息及其标识。现在,转到 Azure 门户并验证消息。它应该显示零条消息,如下图所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/9238b04e-a9c6-4e22-936a-6426f7db059d.png

前面的例子演示了如何使用 Azure 总线服务为我们的微服务发送/接收消息。

我们讨论了通过覆盖一个小应用来实现服务结构上的信息。通过这样做,我们可以使用容器连接服务。我们将在下一节更详细地讨论这一点。

实现关于容器的信息

容器是整个文件系统的一部分。顾名思义,它包含工具、运行时和系统库。容器与同一主机上的其他容器共享其主机操作系统和内核。围绕集装箱的技术并不新鲜。长期以来,它一直是 Linux 生态系统的一部分。由于最近围绕它的基于微服务的讨论,容器技术再次成为人们关注的焦点。请注意,它可以在谷歌、亚马逊和网飞上运行。

Chapter 5

Deploying Microservices with Docker

服务结构上的容器

在前一节中,我们了解到服务结构将服务部署为流程。但是,我们也可以在容器中部署服务。服务结构支持在 Linux 和 Windows 服务器上部署容器,它还支持 Hyper-V 隔离模式。

在本节中,我们将讨论使用 Docker 部署微服务的先决条件和执行计划。

先决条件

要使用示例应用,您应该在系统上设置以下先决条件:

执行

在本节中,我们将简要列出执行点。我们将在第 5 章、用 Docker 部署微服务中编写一个完整的应用。以下是执行点:

  • 创建新应用(我们将创建产品微服务)
  • 创建 Docker 映像
  • 将服务结构添加到前面的项目中
  • 配置服务结构微服务的通信
  • 部署服务结构容器应用

Chapter 5

Deploying Microservices with Docker

在本节中,我们了解了什么是服务结构,并通过查看消息队列、讨论容器以及如何使用 Docker 部署微服务来奠定应用的基础。

下一节将帮助我们理解 Kubernetes 的微服务。

具有立方体的微服务

在前一节中,我们讨论了服务结构和 Docker 容器,并讨论了它们在微服务中的实现步骤。在这里,我们将通过查看 Azure Kubernetes 服务 ( AKS )的概述来与 Kubernetes 讨论微服务。

Azure Kubernetes 服务概述

AKS 基于开源的谷歌 Kubernetes。这是一项在微软 Azure 公共云上提供的服务。因为它是一个托管容器编排服务,所以您可以管理容器(Docker)和基于容器的应用。

https://github.com/kubernetes/kubernetes

https://azure.microsoft.com/mediahandler/files/resourcefiles/phippy-goes-to-the-zoo/Phippy%20Goes%20To%20The%20Zoo_MSFTonline.pdf

AKS 有许多功能,对于已经在其生产环境中发布的应用非常有用。这些功能包括服务命名和发现、负载平衡、应用运行状况检查、水平自动缩放和滚动更新。

重要概念

在开始实施 AKS 之前,我们应该了解一些概念。这些重要的概念将帮助我们充分利用 AKS 的潜力。

Kubernetes 有一个基本单位叫做豆荚。pod 可以包含一个或多个容器,这些容器共享相同的资源,这些资源保证位于主机上。容器部署在 pod 内部,可以通过 localhost 定位。因为这些都是基于集群的,所以每个 pod 在集群中都有自己的 IP 地址。

这些豆荚(豆荚的集合)统称为服务。默认情况下,服务暴露在集群内部,但也可以暴露给集群外部的外部 IP 地址。我们可以使用以下任何可用的特征来公开它:

  • ClusterIP
  • NodePort
  • LoadBalancer
  • ExternalName

Kubernetes 控制器有一种特定类型的控制器,称为复制控制器。在此帮助下,我们可以通过在集群中运行指定数量的 pod 副本来处理复制和扩展。如果底层节点崩溃**,这也可以取代 pod。**

易于部署和管理微服务

AKS 提供了各种功能,可以帮助我们在负载平衡、应用运行状况检查、水平自动缩放和滚动更新方面简化微服务的部署和管理。

下图显示了 AKS 中包含的微服务:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/df4726ed-f30a-464b-89e7-ded7ab8830c1.png

上图显示了以下过程:

  1. 开发人员使用 Visual Studio 将更改提交给 GitHub。

  2. 变更提交,GitHub 将构建推/触发到 Azure DevOps (以前称为 VSTS )。

  3. 在这一步中,DevOps 打包我们的微服务(也称为微服务容器),并将其发送到 Azure 容器注册表。

  4. 在这一步中,容器(打包的微服务)被部署到 Azure Kubernetes 服务集群中。

  5. 用户在应用和网站的帮助下访问这些微服务。

  6. 借助 Azure 活动目录,我们可以防止未经授权的访问。

  7. 数据库在这里是必不可少的,因为微服务从各种各样的数据库中保存和获取数据,例如,SQL 数据库、Azure CosmosDB 或 MySQL 的 Azure 数据库。

  8. 我们有一个专门针对管理员的独立管理门户。

在接下来的章节中,我们将使用 sidecar 和 ambassador 模式来理解我们的应用所需的部署和日志记录,并且我们将看到这些模式如何有助于容器。

边车图案

这个图案的名字指的是摩托车的边车。你可以想象,在一辆摩托车上安装的边车的帮助下,我们可以携带很多东西,而这些东西是我们不可能直接用摩托车携带的。类似地,sidecar 应用在执行不依赖于主应用的外围任务时非常有帮助,但是有助于监视、审核或记录主应用。

sidecar 应用可以是第三方应用;公用事业服务/组件;审计、记录或监控应用;等等。这种模式还为我们提供了将它作为单独的组件或服务进行部署的选项。如果主应用由于不可避免的情况而受阻,例如异常或网络故障,那么我们的 sidecar 应用将是必需的。

让我们考虑以下图表:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/3cc2bcdd-98c9-4300-8b58-c26f2dc5e2ea.png

上图清楚地显示了 sidecar 模式的实现,其中我们将日志保存到主应用的 blob 存储(文件系统)中,并且我们的 sidecar 应用被发送到这些日志中。在这里,我们扩展了主应用的一个特性,即在 sidecar 应用的帮助下,简单地将日志保存在文件系统中,在这里我们可以应用逻辑并分析发送的日志。

何时使用边车模式

这种模式有很多优点。主应用和 sidecar 应用都可以用不同的语言编写,所以没有必要考虑不同的语言或运行时来帮助保持 sidecar 的完整性。主应用可用的资源可以由 sidecar 访问。当 sidecar 应用与主应用通信时,延迟非常小,例如,如果我们将单个 pod 用于主应用和 sidecar 应用。

侧车模式在以下情况下很有用:

  • 当组件或 sidecar 应用及其逻辑/功能可以被使用不同框架用不同语言编写的应用使用时
  • 当组件或功能必须与应用位于同一主机上时

在以下情况下,最好避免这种模式:

  • 当您想要优化进程间通信时。
  • 部署多个应用会产生成本;如果预算有限,不要尝试这种模式。
  • 如果主服务独立于主应用并需要优化,那么这不是您需要的模式。

应遵循的最佳实践

通常,这种模式用于容器,并被称为侧车容器。在应用中实现这种模式时,我们应该遵循规定的最佳实践。以下实践只是基本的最佳实践,可能还有更多特定于应用的实践:

  • 容器应该非常适合边车模式。
  • 在设计 sidecar 应用/组件时,我们应该牢记进程间通信。
  • 当我们决定制作一个 sidecar 应用时,我们应该确保我们已经决定我们需要的 sidecar 功能不会由一个单独的/独立的服务或一个经典的守护程序来实现。

在本节中,我们讨论了使用 sidecar 模式实现日志记录。同样,我们有大使模式,我们将在下一节讨论。

大使模式

大使模式几乎与我们刚刚讨论的边车模式相同。唯一不同的是,每个请求都是通过容器来的,容器也被称为大使(有时也称为代理容器)。换句话说,我们可以说,如果没有大使模式,主应用就无法联系外部世界(用户界面、客户端和/或远程服务)。

让我们考虑以下图表:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/handson-microsvc-cs8-dnet-core3/img/d3320e5d-3eb0-48d4-8e7b-aa3345186925.png

上图清楚地显示了大使模式的实现,其中主应用和大使容器部署在同一个主机上。现在,只要主应用与远程服务交互(这些服务可能是外部 API、用户界面等等),它就通过大使或代理容器进行交互。在这里,大使充当代理,将所有网络请求路由到主应用。

何时使用大使模式

大使模式在以下情况下很有用:

  • 当我们拥有用多种语言或框架开发的多种服务/功能,并且我们需要构建公共客户端连接时
  • 当支持遗留应用或难以修改的应用时
  • 当我们想要标准化和扩展仪器时

在以下情况下最好避免这种模式:

  • 如果有一个高效的网络是优先事项,如果延迟会影响各种操作并且无法处理。
  • 如果客户端的单一语言使用连接功能。更好的选择可能是将它作为一个客户端库,作为一个包分发给开发团队。
  • 当连接特性不能被一般化并且需要与客户端应用更深的集成时。

应遵循的最佳实践

一旦我们开始实施大使模式,就应该遵循一些最佳实践。虽然大使模式和边车模式几乎一样,但是在实现的时候要小心。以下是支持此模式最佳实践的建议要点:

  • 每当我们使用代理或通过任何外部组件路由网络请求时,都会增加延迟。因此,当我们使用大使模式时,我们应该记住延迟开销。
  • 有了这个模式的实现,我们应该考虑是实现断路器还是重试大使,因为这样做可能不安全,除非所有操作都是幂等的。
  • 这种模式最重要的考虑因素是我们如何打包和部署代理。

大使模式帮助我们建立一个连接客户端,将所有网络请求从外部服务路由到主应用。存在延迟的可能性,但是我们在选择这种模式时必须小心,尤其是当我们想要实现的特性没有其他替代方案时。

构建微服务应用时考虑物联网

如今,微服务越来越多地被用作创建企业应用的首选方式。微服务是支持网络、移动和物联网(包括可穿戴设备)等一系列平台和设备的理想选择。在这种情况下,我们的应用 FlixOne 与物联网设备连接。我们可以使用各种组件,比如监视器,它可以用来流式传输数据,可穿戴设备可以用来捕获数据,等等。

物联网是一个具有许多重要用例和巨大潜力的领域。微软 Azure 提供托管云服务,例如连接、监控和控制数十亿物联网资产的物联网 ( 物联网)。为了简化这一点,物联网解决方案有一个或多个运行在云中的物联网设备和一个或多个后端服务,它们相互通信。在构建物联网解决方案时,我们考虑的是大规模企业解决方案。

每个系统都包含一些使系统可靠的基本部分。同样的概念适用于任何物联网解决方案。这些主要部分如下:

  • 设备
  • 后端服务
  • 两者之间的交流

然而,物联网系统的开发带来了巨大的成本,需要在硬件、基础设施和应用方面进行大量投资。

将物联网与微服务结合使用,提供了一种减少基础设施使用和成本的方法。此外,容器化有效地帮助部署微服务,具有最佳的资源利用率,并且它通过各种框架监控整个基础架构,减少了我们的运营开销。因此,在构建物联网解决方案时,需要考虑开发部分,原因如下:

  • 架构:规划和选择你的架构,是构建物联网应用的基础和重要步骤之一。例如,医疗物联网应用的物联网架构将由三个阶段组成:物理阶段(使用传感器等设备)、通信阶段(使用设备收集数据并将其发送给应用)和应用阶段(接收数据并执行各种操作,如分析)。
  • 拓扑和协议:通信协议的选择非常重要,关系到你业务需求的实现。对于设备端通信,Azure IoT Hub 允许的协议包括 MQTT、AMQP、HTTPS 等。星型拓扑是 Azure 中首选的网络拓扑之一。集线器是 Azure 中的虚拟网络,它充当内部设备和 Azure 物联网网络之间的中心连接点。
  • 可扩展性:在开发企业应用的时候,可扩展性始终是重中之重,当我们讨论 IoT 应用的时候,这一点就更加重要了。随着时间的推移,数据和应用的使用量往往会大幅增加,因此我们应该相当重视扩展。Azure 物联网中心提供扩展功能(基本层和标准层),我们可以根据业务需求从中进行选择。
  • 安全性:安全性始终是一个关注点,当我们使用多个设备时,我们应该更加小心我们的安全性实现。确保安全的最佳方法是确保每个连接的设备都有自己的身份验证。
  • 测试部署:设备要测试好再部署。我们应该验证每个设备都与其物联网集线器连接并通信。性能测试(或负载测试)将是分析物联网应用的最佳方式。

在接下来的章节中,我们将讨论物联网中心和物联网边缘。

物联网枢纽概述

物联网中心是托管在云上的服务(受管);它就像一个中介,位于物联网应用和应用管理的设备之间。

当我们使用 Azure 物联网时,我们有能力在云上托管的后端系统的帮助下,在物联网应用和与之相关联的数百万台设备之间进行通信。例如,您可以将遥测数据从您的设备发送到 Azure IoT Hub 通过我们在云中的应用,我们可以根据业务模型执行分析或其他操作。我们不涉及这个例子的实现部分,因为它超出了本书的范围。不过完整实现可以参考这个链接:https://docs . Microsoft . com/en-us/azure/IOT-hub/quick start-send-telemetry-dotnet

此外,任何设备都可以与物联网集线器虚拟连接,并且它将支持双向通信:设备到云和云到设备。物联网中心允许我们管理可扩展、功能全面的物联网解决方案,因此我们可以做到以下几点:

  • 扩展解决方案:物联网集线器提供了扩展众多连接设备的设施。与扩展解决方案相关的重要一点是,它提供了不同的扩展选项,因为它认识到每个物联网解决方案都是不同的。如前所述,根据物联网解决方案的要求,您可以选择基本价格层或标准价格层。
  • 保护通信:物联网集线器还可以促进安全的通信通道,因为每个连接的设备都有自己的身份验证。
  • 与其他设备集成:IoT Hub 最棒的地方就是可以连接各种 Azure 服务,像 Azure 事件网格、Azure 逻辑 app、Azure 流分析等等。

物联网边缘概述

Azure 物联网边缘有助于设备的业务逻辑和分析。这样,我们就不需要为数据管理费心了。我们只需要专注于业务逻辑和工作解决方案。它还有助于打包业务逻辑,并帮助我们进入标准容器,以便我们能够横向扩展物联网解决方案。由于业务逻辑是在容器中维护的,我们可以将这些容器部署到任何设备上,然后从云中监控它们。

Azure 物联网边缘由三个组件组成:

  • 物联网边缘模块:Azure 物联网边缘模块是由 Azure 物联网边缘管理的最小计算单元。它可以包含不同的 Azure 服务,例如 Azure 流分析。根据业务需求,它还可以包含定制的代码。
  • 物联网边缘运行时:该模块在物联网边缘设备上启用定制逻辑和云逻辑。它执行管理和通信操作。它还有助于在设备上安装和更新工作负载。
  • 基于云的界面:它提供了从任何地方远程管理和监控物联网设备的设施。

要开发、部署和维护这些模块,需要以下四个要素:

  • 模块映像:这是一个定义一个模块的多个软件的包。
  • 模块实例:这是物联网边缘运行时发起的实例;实例是执行模块映像的特定代码或计算。
  • 模块标识:这是存储在物联网集线器上的安全信息(包括任何其他所需信息)。
  • 模块双胞胎:这包括元数据和配置信息,以 JSON 文件的形式;它存储在物联网集线器上。

本节的目的是向您概述微服务物联网的各种考虑因素。在这里,我们讨论了物联网中心和物联网边缘,我们理解一个是托管服务,而另一个允许我们添加业务逻辑并对物联网设备进行分析。

摘要

服务间的通信可以通过同步或异步通信来实现,这是一种协作方式。微服务应该有异步 API。应用编程接口网关是一个代理服务器,允许各种客户端与应用编程接口交互。作为一个应用编程接口网关,Azure 应用编程接口管理提供了大量的功能,我们可以使用这些功能来管理和托管各种 RESTful 应用编程接口。有多种模式可以帮助我们与微服务进行通信。

通过使用 Azure 总线服务,我们可以使用 Azure 总线服务消息队列轻松管理和处理服务间通信。(服务可以使用它轻松地在它们之间发送或接收消息。)最终的一致性都是关于具有高可伸缩性的可伸缩系统,可以用 CAP 定理来证明。

在下一章中,我们将讨论各种测试策略,这样我们就可以测试我们的应用并构建在微服务架构风格上。

问题

  1. 什么是同步通信和异步通信?
  2. 什么是集成模式?
  3. 什么是事件驱动模式,为什么它对微服务如此重要?
  4. 什么是 CAP 定理?

进一步阅读

ADO数据库编程中文参考手册详解 本文还有配套的精品资源,点击获取 简介:ADO是一种由Microsoft开发的数据访问技术,广泛应用于Windows平台,以实现与多种数据库系统的交互。本手册详细介绍了ADO的核心概念、主要组件以及数据库编程的详细步骤和高级特性。手册内容包括Connection、Command、Recordset等对象的使用方法,以及如何在VB6、VBA等环境中执行高效的数据操作。此外,还... 阅读详情

相关推荐

微服务教程

微服务搭建教程,欢迎大家下载。跟着文档一步一步搭建就可以。

精通 Azure 身份和访问管理(四)

本章节中,我们讨论并配置了用于发现运动中、传输中和静态数据的关键技术。你了解到这些任务的重要性,以及它们为你的环境提供的数据控制和优势。它们赋予你构建高效检测和数据泄露防护规则的能力。我们将在第十五章,配置 Azure 信息保护解决方案中利用这些知识,构建 Azure 信息保护解决方案。但首先,我们将学习更多关于 Azure RMS 密钥的信息,这是你理解保护过程并找到适当工具排查错误所必需的。在下一章节中,你将学习 Azure RMS 密钥的使用方式以及针对不同合规性要求可用的部署模型。

龙哥盟 1853

UniTouch:多模态触觉对齐技术如何重塑零样本跨模态应用

UniTouch模型通过创新的多模态触觉对齐技术,成功将触觉数据映射到通用的视觉-语言嵌入空间,解决了触觉模态的“孤岛困境”。该技术利用对比学习和传感器特定标记,实现了高质量的零样本学习能力,使模型无需额外训练即可完成触觉分类、描述等任务,并能驱动跨模态生成,为机器人、VR和智能制造等领域提供了强大的触觉理解新范式。

weixin_29044713的博客 409

【ASP.NET CORE】 4. 集成配置系统、分层架构

本文基于杨中科《ASP.NET Core技术内幕与项目实战》梳理了ASP.NET Core的核心知识点。

WSSsharon的博客 695

电源完整性之Cadence Sigrity Power DC_电热协同仿真

之前和大家分享过电源完整性之仿真设计原理链接: link.今天接着上一篇文章总结一下电源直流压降的的仿真操作流程及一些simulation中的设置参数,用到的是Cadence Sigrity 中的Power DC组件,来操作一下电热协同仿真,下面开始享受仿真的快乐吧: 第0步:仿真简介 电热协同仿真把电和热相互的影响考虑到了一起。因为电导率并不是固定不变的,而是和温度有关,随着温度的升高导电率会下降。同样电流流径电阻的时候会产生热量,此热量为焦耳热。因此为了获得准确的仿真信息,应考虑电与热的互相影响,进行电

刘小同学的博客 1万+

三菱FX5U运动控制(步进)

三菱FX5U脉冲定位控制,原点回归,相对定位,绝对定位,运动控制案例。

weixin_55762091的博客 1万+

使用net core3.1操作数据库

这里写自定义目录标题使用net core3.1操作数据库需要安装的包 使用net core3.1操作数据库 需要安装的包 <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.1" /> <Pac...

秦秋雨的专栏 4346

AIGC大模型应用教学平台

该平台采用最新的AI推理加速芯片,提供30Tops算力和32GB存储,能够运行常见的大语言模型如ChatGLM2、Llama2、Baicuan、Mistral等,也可以运行文生图扩散模型Stable Diffusion,提供大模型微调、量化、转换和加速推理服务。在此基础上,平台还提供常见的生成式AI应用,包括AIGC知识抽取增强(RAG)、AIGC绘画、AIGC会话聊天、数字虚拟人等应用,以便学生能够掌握最新的生成式AI模型微调、提示工程和智能体应用技能。

中智讯的博客 1208

深度解析.NET中ASP.NET Core中间件:构建高效Web应用的关键环节

在ASP.NET Core开发领域,中间件扮演着至关重要的角色,它是构建高效、灵活Web应用的关键组件。理解ASP.NET Core中间件的工作原理、执行机制以及如何正确使用它,对于开发者优化Web应用性能、实现复杂功能至关重要。

dotnethello的博客 3178

C#利用S7netplus实现西门子S7_200_SMART PLC高效点位监控与操作

本文详细介绍了如何使用C#结合S7netplus开源库高效监控与操作西门子S7-200 SMART PLC的点位。内容涵盖从开发环境搭建、建立通信连接,到核心的点位读写、批量数据处理及字节与位数组转换等关键技术,并提供了构建稳定监控程序的架构与避坑指南,帮助开发者快速实现上位机与PLC的稳定数据交互。

rum55的博客 316

SoC芯片时钟树优化实战:如何用分段CTS解决DDRPHY与Memory Controller的时序难题

本文深入探讨了SoC芯片时钟树优化中的分段CTS技术,重点解决DDRPHY与Memory Controller之间的时序难题。通过分析多域时钟架构的复杂性,提出三级分段CTS策略,并结合28nm移动SoC实战案例,展示如何通过Guide Buffer插入和本地段重构实现72%的跨域偏差改善。文章还涵盖5nm以下工艺的线阻变异、热梯度效应等特殊考量,为数字IC设计提供时钟树综合的高阶解决方案。

kkk56的博客 905

C#8 和 .NET Core3 微服务实用手册(三)

在这一章中,我们已经介绍了一些可以帮助我们构建健壮的基于微服务的系统的模式。在这本书里,我们已经介绍了许多例子,这些例子都是基于本章讨论的模式。我们通过观察微服务生态系统中模式的角色和实践,遵循了每个模式的描述。我们从聚合器模式开始,解释复合微服务,然后使用我们想象中的 FlixOne 商店应用中的一个例子来实现这个模式。然后,我们采用了共享数据模式和反腐败层模式。我们在应用的上下文中讨论了这些模式的最佳实践,并基于它们实现了解决方案。在下一章中,我们将创建一个基于微服务架构设计的应用。

龙哥盟 995

lammps构建高熵合金模型+结构优化初步筛选能量最小的结构

lammps构建高熵合金模型+结构优化初步筛选能量最小的结构

qq_45834835的博客 2604

C#8 和 .NET Core3 Azure 项目(一)

.NET Core 是. NET 的通用、模块化、跨平台和开源实现.NET Core 3 具有改进的性能以及对桌面应用的支持。.NET Core 3 不仅应该吸引新开发人员开始学习框架,还应该说服旧开发人员开始迁移他们的应用。这本书是第二版 C# 7 和.NET Core 2.0 蓝图,更新了 C# 8 和的最新特性和增强功能.NET Core 3.0。这本书是一个全面的指南,提供了 10 个真实世界的企业应用。它将帮助您同时学习和实现这些概念,并通过在 ASP.NET Core 和 Azure 上构建满足

龙哥盟 1783

【产品】 产品设计:如何解决铝合金氧化着色后表面硬度低的问题(硬质氧化)

1、氧化处理技术 1)化学氧化 化学氧化处理所获得的膜层较薄,一般厚度为0.5μm ~4μm,质软不耐磨,抗蚀能力低于阳极氧化膜。 一般不宜单独便用,由于化学氧化膜吸附能力好,主要作用于油漆的底层,化学氧化按其溶液性可分为碱性和酸性两种。按其膜层性质可分为氧化膜,磷酸盐膜、铬酸盐膜及磷酸一铬酸盐膜等(这类型的氧化膜是可导电)。 2)电化学氧化(阳极氧化) 电化学氧化(俗称阳极氧化)是铝及...

产品线负责人 9736

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

C#8 和 .NET Core3 软件架构实用指南(一)

本书涵盖了软件架构中涉及的最常见的设计模式和框架。它通过提供实际的真实场景,讨论何时以及如何使用每个模式。本书还介绍了 DevOps、微服务、持续集成和云计算等技术流程,以便你能够为你的客户提供一流的软件解决方案。本书将帮助你理解客户希望从你这里得到的产品。它将指导你解决开发过程中可能遇到的最大问题。它还涵盖了你在基于云的环境中管理应用时需要遵循的“做”和“不做”事项。你将了解不同的架构方法,如分层架构、面向服务的架构、微服务和云架构,并了解如何将它们应用于特定的业务需求。最后,你将使用 Azure 在远程

龙哥盟 1115

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

C#8 和 .NET Core3 微服务实用手册(二)

测试微服务与测试以传统架构风格构建的应用有点不同。在. NET 单片应用中,与微服务相比,测试稍微容易一些,并且它提供了实现独立性和短的交付周期。微服务在测试时面临挑战。借助测试金字塔概念,我们可以制定测试程序的策略。就测试金字塔而言,我们可以很容易地看到,单元测试允许我们测试一个类的一个小函数,并且耗时更少。另一方面,测试金字塔的顶层范围很大,有系统测试或端到端测试,这些测试既耗时又非常昂贵。消费者驱动的契约是测试微服务的非常有用的方法。Pact-net 是一个用于此目的的开源工具。

龙哥盟 1493

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

.NET Core 和 .NET Framework 的不同

一、各自优势 .NET Core的优势: 如果您正在构建一个新应用程序并在 .NET Core 和 .NET Framework 之间进行选择,那么 .NET Core 是您的最佳选择。 微软刚刚发布了 .NET Core v 3.0,它是 .NET Core 的一个大大改进的版本。如果您想为未来学习和构建,那么 .NET Core 就是您的最佳选择。 .NET 3.0 现在支持 WPF 和 Windows 窗体。.NET Core 3.0 还支持 UWP、WPF 和 Windows 窗..

weixin_50498482的博客 1万+

C#8 和 .NET Core3 并行编程实用指南(三)

在之前的章节中,我们已经看到并行编程是如何工作的。并行性是关于创建称为工作单元的小任务,可以由一个或多个应用程序线程同时执行。由于线程在应用程序进程内运行,它们在使用委托通知调用线程完成后通知调用线程。在本章中,我们将首先介绍同步代码和异步代码之间的区别。然后,我们将讨论何时使用异步代码以及何时避免使用它。我们还将讨论异步模式如何随时间演变。最后,我们将看到并行编程中的新特性如何帮助我们解决异步代码的复杂性。在本章中,我们将涵盖以下主题:同步与异步代码何时使用异步编程何时避免异步编程。

龙哥盟 960

微服务架构实战指南:ASP.NET Core微服务开发

欢迎来到ASP.NET Core微服务开发的深度实践之旅。本教程基于[aspnetrun/run-aspnetcore-microservices](https://github.com/aspnetrun/run-aspnetcore-microservices)这一开源项目,它展示如何在.NET平台上构建复杂的微服务体系。本项目融合了最新的.NET 8特性与C# 12,覆盖了从垂直架构到清洁架

gitblog_00997的博客 1051

【C#】.Net Core基础入门到实例部署

Net Core 入门基础,项目实例代码,部署IIS

qwerty_funny的博客 4753

一文搞懂.NET Core、.NET 5、.NET 6、.NET 7、.NET 8 和 .NET 9 的区别

.NET是微软推出的跨平台开发框架,支持C#、F#等语言,可开发Web、桌面、移动等多种应用。从.NET Core(2016)开始实现跨平台,到.NET 5(2020)统一技术栈,再到.NET 6(2021)首个LTS版本支持全平台开发。最新.NET 8(2023 LTS)具备AI/ML支持、Blazor United等特性,性能优化显著。建议新项目优先选择.NET 8,其长期支持至2026年,功能完善且稳定。.NET 9(2024)将聚焦AI集成,但作为短期支持版本更适合技术尝鲜。

GuoGuoABC 2474

C#/.NET/.NET Core推荐学习书籍(24年4月更新,已分类)

古人云:“书中自有黄金屋,书中自有颜如玉”,说明了书籍的重要性。作为程序员,我们需要不断学习以提升自己的核心竞争力。以下是一些优秀的C#/.NET/.NET Core相关学习书籍(包含了等),值得.NET开发者们学习和专研。书籍已分类,欢迎大家PR分享自己觉得不错的C#/.NET/.NET Core宝藏书籍。注意:公众号无法附加外链,大家可以打开GitHub收录地址查看书籍详细描述及其书籍预览、阅读地址。

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 8201

C#8 和 .NET Core3 软件架构实用指南(三)

ASP.NET Core 应用程序是基于我们在第五章将微服务架构应用于您的企业应用程序的使用通用宿主子节中描述的Host概念的 .NET Core 应用程序。每个 ASP.NET 应用程序的program.cs});();});设置了一个标准的宿主,而则配置它以处理 HTTP 管道。更具体地说,它执行以下操作:它设置了接口的属性为当前目录。它从和加载配置信息。一旦加载,JSON 对象属性中包含的配置信息可以使用 ASP.NET Core 选项框架映射到 .NET 对象属性。

龙哥盟 1280

.NET Core 8 与 RabbitMQ 和 MassTransit

本指南详细介绍了在.NET Core 8环境中集成RabbitMQ和MassTransit的完整流程。主要内容包括:1)安装.NET Core 8 SDK、RabbitMQ和MassTransit;2)RabbitMQ管理控制台的基本配置;3)项目结构设计,包含发送服务(QueueSenderService)和接收服务(QueueReceiverService);4)MassTransit的核心配置方法;5)三种典型消息模式实现(命令发送、事件发布、请求响应)。文中提供了完整的代码示例和配置说明,并建议开发

csdn_aspnet的专栏,请点击博客主页右上角三个点中的私信联系 6556

【亲测免费】 开源项目推荐:基于.NET 8的微服务架构实践

本项目是一个开源的微服务架构实践项目,名为“run-aspnetcore-microservices”,托管在GitHub上。该项目使用了.NET平台,主要编程语言为C#,同时结合了最新的.NET 8和C# 12的特性。项目通过ASP.NET Web API、Docker、RabbitMQ、gRPC等多种技术,实现了微服务架构在真实世界电子商务场景中的应用。 ## 2. 项目核心功能 项目的核...

gitblog_00717的博客 968
上一篇: Unity 5.x 2D 游戏开发入门指南(三)
下一篇: C#8 和 .NET Core3 微服务实用手册(二)
绝不原创的飞龙
绝不原创的飞龙 优质创作者: 人工智能、数据科学与机器学习技术领域 优质创作者: 人工智能、数据科学与机器学习技术领域
博客等级 码龄15年 4万+粉丝 2万+原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值