vlambda博客
学习文章列表

使用.net中的API网关模式封装微服务


在本文中,我们将了解如何使用API网关模式来封装微服务并抽象出底层实现细节,从而允许使用者拥有进入我们系统的一致入口点。

为了构建和测试我们的应用程序,我们需要:

1.Visual Studio 2019

2..NET Core 5 SDK

由于微服务是一个相当复杂的主题,在我们进入下一节的代码之前,让我们花点时间解释一下基础知识。

微服务是一种架构风格,因此这种风格的实现可能会有很大的差异,并且经常是一个备受争议的话题。然而,大多数专家认为微服务具有以下属性:

  • 松散耦合
  • 容易维护
  • 独立部署
  • 可测试
  • 围绕业务能力组织

01

庞然大物 vs 微服务

让我们考虑下面的图表来比较庞然大物和微服务架构:

我们大多数人都熟悉“庞然大物”体系结构,其中单个应用程序(通常包含多个逻辑“层”,如UI/业务/数据)作为单个流程部署到一个或多个服务器上。

微服务体系结构试图将应用程序分解为更小的部分,并独立部署它们。分解应用程序的过程是一个复杂而重要的过程,超出了本文的范围,因此在这里推荐一些关于领域驱动设计(DDD)的研究。

描述庞然大物和微服务体系结构之间的区别本身就是一个主题,由于我们在前面已经谈到了微服务的一些好处,所以这里就不做太多的详细讨论了。就本文而言,我们想要关注的主要好处是独立性。也就是说,我们将看到改变一个微服务是如何不会使整个应用程序崩溃的,因为其他微服务甚至不需要关心这个变化。这与庞然大物架构有很大的不同,在庞然大物架构中,任何微小的更改都需要关闭并更新整个应用程序。

02

分解我们的庞然大物

我们的示例庞然大物应用程序,解决方案如下:

使用.net中的API网关模式封装微服务

我们可以看到它是一个简单的ASP.NET Core API,为Authors和Books分别提供一个控制器。

让我们按下CTRL+F5在浏览器中运行应用程序,它将调用" /books ":

使用.net中的API网关模式封装微服务

我们可以看到它返回了一个书单。

让我们把浏览器中的URL改为/authors:

使用.net中的API网关模式封装微服务

创建AuthorsService

首先,让我们通过添加一个新的ASP.NET Core API来创建AuthorsService:

使用.net中的API网关模式封装微服务

现在,我们可以进行一些重构,将相关代码从提取到新的AuthorsService中。

让我们复制:

1.Controllers文件夹中的AuthorsController

2.Models文件夹中的Author

3.Data文件夹中的Repository

我们应该以以下结构结束:

使用.net中的API网关模式封装微服务

接下来,我们将进行一些调整以启动并运行AuthorsService。

首先,让我们在Startup.cs中的ConfigureServices方法中添加以下代码:

services.AddSingleton<Repository>();

接下来,让我们删除Repository中对Book的引用,最后得到:

public class Repository{ private IEnumerable<Author> _authors { get; set; } public Repository() { _authors = new[] { new Author { AuthorId = 1, Name = "John Doe" }, new Author { AuthorId = 2, Name = "Jane Smith" } }; } public IEnumerable<Author> GetAuthors() => _authors;}

现在,我们可以运行新的AuthorsService,并将URL指向“/authors”路径:

使用.net中的API网关模式封装微服务

太棒了!我们已经启动并运行了作者微服务。

创建BooksService

现在,让我们按照上面完全相同的步骤,将相关的Books代码从这个庞然大物提取到一个新的BooksService API中。我们将在这里再次重复所有的步骤,因为这应该是不言而喻的。

让我们运行这个新服务,并将URL指向“/books”路径:

使用.net中的API网关模式封装微服务

现在,我们的Books微服务已经启动并运行。

现在,我们可以在两个不同的服务器上独立部署这两个微服务,并公开它们使用。它们没有以任何方式耦合在一起。

但是在下一节中,我们将讨论如何使用API网关模式这种方法解决一些问题。

03

API网关模式

如果有人想要使用我们的庞然大物API,这非常简单,因为只有一个主机需要处理,例如“https://ourmonolithapi.ourcompany.com”

然而,现在我们已经把这个庞然大物提取成两个独立的微服务,这变得更加困难,因为消费者需要与多个api交互:

使用.net中的API网关模式封装微服务

出现的问题包括:

1.每个服务都在哪里?(发现)

2.这些服务使用相同的协议吗?(复杂性)

3.我需要多少次往返才能呈现包含Books和Authors的页面?(性能)

API网关模式有哪些帮助

这就是API网关模式的作用所在,它通过坐在微服务的“前面”,将消费者与所有这些问题隔离开来:

使用.net中的API网关模式封装微服务

现在回到我们之前的问题:

每个服务都在哪里?回答:消费者不在乎。它只处理API网关,然后API网关代表消费者与服务进行交互

这些服务使用相同的协议吗?回答:消费者也不在乎。如果微服务使用不同的协议(例如,如果我们想将BooksService改为使用gRPC而不是HTTP), API网关就有责任在这些协议之间进行映射。

我需要多少次往返才能使这个页面同时包含Books和Authors?答:API网关可以为消费者“聚合”这些调用。这是特别重要的,因为我们不能控制用户的网络(他们可能在一个缓慢的连接上),但是,我们可以控制我们的内部网络。因此,通过将API网关和微服务紧密结合在一起,我们可以最大化网络效率。

还有一个类似的模式值得一提,称为“BFF”模式。不,这并不意味着永远是最好的朋友(best friends forever),而是“后端为前端”(Backend-for-frontend)。这个模式与API网关非常相似,但是它涉及到为每个“前端”创建一个API网关。

这张图说明了我们如何为我们的用例实现BFF模式:

使用.net中的API网关模式封装微服务

在这种情况下,每个客户端都有自己的BFF / API网关,其功能可以为特定的客户端量身定制。例如,也许web应用程序需要一些搜索引擎优化功能,而移动应用程序并不关心。这允许移动和web团队自主操作,并拥有自己的数据层。

然而,在本文中,我们只关注单个消费者的单个API网关。

我们如何实现API网关模式?

有很多方法可以实现API网关模式,包括使用现成的软件。最终的选择将取决于API网关所需的特性。

我们可能需要的一些常见功能包括:

1.身份验证

2.限制速度

3.分析

4.日志记录

5.文档

Traefik、Kong和Azure API Management等服务可以提供这些特性的全部或部分,因此,根据你的用例需要什么,有必要仔细阅读它们。

然而,出于本文的目的,我们将排除这些特性,而将重点放在最基本的功能上,即消费者和微服务之间的一个简单的基于http的代理。为了实现这一点,我们可以很容易地自己用ASP.NET Core来实现。

添加我们的API网关

因为我们已经在我们的解决方案中有了庞然大物API,我们计划使用ASP.NET Core作为我们的API网关,我们可以简单地将它转换成我们想要的。

首先,让我们删除“Controllers”、“Data”和“Models”文件夹,因为我们不再需要它们了。

接下来,让我们在Startup中向ConfigureServices()添加以下一行:

services.AddHttpClient();

这样我们就可以通过HTTP客户端调用新的微服务。

接下来,让我们添加一个名为ProxyController的新控制器:

[Route("[action]")][ApiController]public class ProxyController : ControllerBase{ private readonly HttpClient _httpClient; public ProxyController(IHttpClientFactory httpClientFactory) { _httpClient = httpClientFactory.CreateClient(); } [HttpGet] public async Task<IActionResult> Books() => await ProxyTo("https://localhost:44388/books"); [HttpGet] public async Task<IActionResult> Authors() => await ProxyTo("https://localhost:44307/authors"); private async Task<ContentResult> ProxyTo(string url) => Content(await _httpClient.GetStringAsync(url));}

代码应该是相当不言自明的,但本质上我们使用HttpClient来调用我们的新微服务并直接返回响应。

让我们在解决方案中构建并运行我们所有的三个项目,我们应该有:

https://localhost: 44307(Authors)

https://localhost: 44388(Books)

https://localhost: 5001 (API网关)

运行完所有程序后,让我们打开浏览器https://localhost:5001/books

使用.net中的API网关模式封装微服务

同样,让我们导航到https://localhost:5001/authors:

使用.net中的API网关模式封装微服务

我们现在有了一个功能正常的API网关,可以将请求代理到我们的两个微服务!

在下一节中,让我们添加API网关的一个简单消费者。

添加一个简单的消费者

现在我们有了一个API网关,让我们来看看如何添加一个简单的web页面,它可以使用我们的API。

在此之前,我们需要在API网关中启用CORS,以便能够发出跨域请求。

让我们打开Startup.cs并修改ConfigureServices():

services.AddCors(options =>{ options.AddDefaultPolicy(builder => { builder.AllowAnyOrigin(); });});

出于测试目的,我们允许任何来源的CORS。但是对于生产应用程序,我们需要更严格的CORS策略。

接下来,让我们将CORS中间件添加到ASP.NET Core管道中的Configure()方法:

app.UseCors();

让我们变异并运行我们的应用程序,以部署新的更改。

接下来,让我们创建一个非常简单的HTML页面:

<html><head></head><body> <button onclick="callAPI('books')">Get Books</button> <button onclick="callAPI('authors')">Get Authors</button>
<script type="text/javascript"> function callAPI(path) { let request = new XMLHttpRequest(); request.open("GET", "https://localhost:5001/" + path); request.send(); request.onload = () => { if (request.status === 200) { alert(request.response); } else { alert(`Error: ${request.status} ${request.statusText}`); } } }</script></body></html>

这里我们有几个按钮,当点击时将调用我们的API网关并通知结果。

让我们在浏览器中打开HTML页面,点击“Get Books”按钮,我们看到一个浏览器弹出:

使用.net中的API网关模式封装微服务

让我们点击“Get Authors”按钮:

使用.net中的API网关模式封装微服务

我们看到消费者能够调用我们的API网关,而API网关又能够将这些请求代理到相关的微服务。

在下一节中,让我们看看如何更改我们的一个微服务,这将突出微服务API网关模式的好处之一。

改变Microservice

我们在前面讨论了微服务应该如何独立部署,以及更改一个微服务不需要关闭整个应用程序(在本例中,“应用程序”就是API网关)。

我们可以通过改变“Authors”微服务来证明这一理论。

首先,让我们“关闭”Authors的微服务,停止IIS Express中的站点:

使用.net中的API网关模式封装微服务

如果我们再次点击“Get Authors”按钮,我们会看到一个错误:

使用.net中的API网关模式封装微服务

然而,如果我们点击“Get Books”按钮:

正如预期的那样,它仍然返回一个结果。

现在,我们将对Authors微服务进行一些更改。

改变Authors的微服务

首先,让我们为Author类添加一个新属性:

public string Country { get; set; }

接下来,让我们更新我们的Repository,以返回我们的新字段:

_authors = new[]{ new Author { AuthorId = 1, Name = "John Doe", Country = "Australia" }, new Author { AuthorId = 2, Name = "Jane Smith", Country = "United States" }};

这是一个微不足道的更改,但关键是,更改可以是任何东西:来自新数据库的新字段、升级包,甚至用不同的编程语言重写整个应用程序。只要遵守了微服务和API网关之间的契约(例如没有破坏性的更改),就不需要发生任何其他事情。

让我们再次构建并运行我们的Authors服务,然后点击“Get Authors”按钮:

我们看到我们的新变化正在传递给消费者。

值得在此驻足片刻,看看我们取得了哪些成就:

1.我们创建了一个消费者,它通过一个应用程序(API网关)与两个微服务交互。

2.我们关闭了其中一个微服务并进行了更新

3.另一个微服务能够继续提供功能

考虑一下我们的用户是否功能更丰富,例如,列出了所有的Book。如果该特性不需要任何Author功能,则可以继续为用户提供该特性,甚至对作者进行更新。

这是非常强大的,因为它允许“优雅”地降低某些功能,同时保留其他功能。我们甚至可以为此向API网关添加更多特性,例如,检测下游服务的中断并返回缓存的数据,或利用备份功能。关键是,我们的后端消费者通过API网关与后台的任何更改隔离开来。

04

结论

在本文中,我们简要介绍了微服务和API网关模式背后的理论,并创建了一个非常简单的设置,演示了一些关键概念。

尽管我们的实现非常基本,但重要的是我们所设置的可能性。我们能够继续为我们的用户提供应用程序的一些功能,尽管我们正在对其他功能进行一些升级。这在微服务中很常见,它们可能是不同的团队,从事不同的功能和微服务。使用这种方法,升级功能的团队负责更新,而不需要在其他团队之间进行广泛的协调。

API网关为我们的后端提供了一个很棒的“面子”,可以很容易地控制如何将我们的后端功能服务给我们的消费者,而不必将这些关注点推到下游的每个服务。这意味着实际的服务要灵活得多,只需要关心它们需要交付给API网关的内容。

下一个逻辑步骤是建立更多的微服务,提高API网关等更多的功能,比如日志,分析和弹性,然而,每一个点本质上是一个新课题。

希望你喜欢这篇文章。编码快乐!