使用.net中的API网关模式封装微服务
在本文中,我们将了解如何使用API网关模式来封装微服务并抽象出底层实现细节,从而允许使用者拥有进入我们系统的一致入口点。
为了构建和测试我们的应用程序,我们需要:
1.Visual Studio 2019
2..NET Core 5 SDK
由于微服务是一个相当复杂的主题,在我们进入下一节的代码之前,让我们花点时间解释一下基础知识。
微服务是一种架构风格,因此这种风格的实现可能会有很大的差异,并且经常是一个备受争议的话题。然而,大多数专家认为微服务具有以下属性:
-
松散耦合 -
容易维护 -
独立部署 -
可测试 -
围绕业务能力组织
01
—
让我们考虑下面的图表来比较庞然大物和微服务架构:
我们大多数人都熟悉“庞然大物”体系结构,其中单个应用程序(通常包含多个逻辑“层”,如UI/业务/数据)作为单个流程部署到一个或多个服务器上。
微服务体系结构试图将应用程序分解为更小的部分,并独立部署它们。分解应用程序的过程是一个复杂而重要的过程,超出了本文的范围,因此在这里推荐一些关于领域驱动设计(DDD)的研究。
描述庞然大物和微服务体系结构之间的区别本身就是一个主题,由于我们在前面已经谈到了微服务的一些好处,所以这里就不做太多的详细讨论了。就本文而言,我们想要关注的主要好处是独立性。也就是说,我们将看到改变一个微服务是如何不会使整个应用程序崩溃的,因为其他微服务甚至不需要关心这个变化。这与庞然大物架构有很大的不同,在庞然大物架构中,任何微小的更改都需要关闭并更新整个应用程序。
02
—
分解我们的庞然大物
我们的示例庞然大物应用程序,解决方案如下:
我们可以看到它是一个简单的ASP.NET Core API,为Authors和Books分别提供一个控制器。
让我们按下CTRL+F5在浏览器中运行应用程序,它将调用" /books ":
我们可以看到它返回了一个书单。
让我们把浏览器中的URL改为/authors:
创建AuthorsService
首先,让我们通过添加一个新的ASP.NET Core API来创建AuthorsService:
现在,我们可以进行一些重构,将相关代码从提取到新的AuthorsService中。
让我们复制:
1.Controllers文件夹中的AuthorsController
2.Models文件夹中的Author
3.Data文件夹中的Repository
我们应该以以下结构结束:
接下来,我们将进行一些调整以启动并运行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”路径:
太棒了!我们已经启动并运行了作者微服务。
创建BooksService
现在,让我们按照上面完全相同的步骤,将相关的Books代码从这个庞然大物提取到一个新的BooksService API中。我们将在这里再次重复所有的步骤,因为这应该是不言而喻的。
让我们运行这个新服务,并将URL指向“/books”路径:
现在,我们的Books微服务已经启动并运行。
现在,我们可以在两个不同的服务器上独立部署这两个微服务,并公开它们使用。它们没有以任何方式耦合在一起。
但是在下一节中,我们将讨论如何使用API网关模式这种方法解决一些问题。
03
—
API网关模式
如果有人想要使用我们的庞然大物API,这非常简单,因为只有一个主机需要处理,例如“https://ourmonolithapi.ourcompany.com”
然而,现在我们已经把这个庞然大物提取成两个独立的微服务,这变得更加困难,因为消费者需要与多个api交互:
出现的问题包括:
1.每个服务都在哪里?(发现)
2.这些服务使用相同的协议吗?(复杂性)
3.我需要多少次往返才能呈现包含Books和Authors的页面?(性能)
API网关模式有哪些帮助
这就是API网关模式的作用所在,它通过坐在微服务的“前面”,将消费者与所有这些问题隔离开来:
现在回到我们之前的问题:
每个服务都在哪里?回答:消费者不在乎。它只处理API网关,然后API网关代表消费者与服务进行交互
这些服务使用相同的协议吗?回答:消费者也不在乎。如果微服务使用不同的协议(例如,如果我们想将BooksService改为使用gRPC而不是HTTP), API网关就有责任在这些协议之间进行映射。
我需要多少次往返才能使这个页面同时包含Books和Authors?答:API网关可以为消费者“聚合”这些调用。这是特别重要的,因为我们不能控制用户的网络(他们可能在一个缓慢的连接上),但是,我们可以控制我们的内部网络。因此,通过将API网关和微服务紧密结合在一起,我们可以最大化网络效率。
还有一个类似的模式值得一提,称为“BFF”模式。不,这并不意味着永远是最好的朋友(best friends forever),而是“后端为前端”(Backend-for-frontend)。这个模式与API网关非常相似,但是它涉及到为每个“前端”创建一个API网关。
这张图说明了我们如何为我们的用例实现BFF模式:
在这种情况下,每个客户端都有自己的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的新控制器:
[ ]
[ ]
public class ProxyController : ControllerBase
{
private readonly HttpClient _httpClient;
public ProxyController(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient();
}
[ ]
public async Task<IActionResult> Books()
=> await ProxyTo("https://localhost:44388/books");
[ ]
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
同样,让我们导航到https://localhost:5001/authors:
我们现在有了一个功能正常的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”按钮,我们看到一个浏览器弹出:
让我们点击“Get Authors”按钮:
我们看到消费者能够调用我们的API网关,而API网关又能够将这些请求代理到相关的微服务。
在下一节中,让我们看看如何更改我们的一个微服务,这将突出微服务API网关模式的好处之一。
改变Microservice
我们在前面讨论了微服务应该如何独立部署,以及更改一个微服务不需要关闭整个应用程序(在本例中,“应用程序”就是API网关)。
我们可以通过改变“Authors”微服务来证明这一理论。
首先,让我们“关闭”Authors的微服务,停止IIS Express中的站点:
如果我们再次点击“Get Authors”按钮,我们会看到一个错误:
然而,如果我们点击“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网关等更多的功能,比如日志,分析和弹性,然而,每一个点本质上是一个新课题。
希望你喜欢这篇文章。编码快乐!