使用 ASP.NET Core 构建微服务






4.89/5 (66投票s)
微服务及其相对于单体服务架构的优势
微服务一词描述了一种软件开发风格,它源于当代趋势,旨在确立能够提高大规模开发和管理软件解决方案的速度和效率的实践。微服务更多的是应用一定的原则和架构模式,而不是架构本身。每个微服务都独立存在,但另一方面,它们也相互依赖。项目中的所有微服务都可以以自己的节奏、在本地、在云端、独立地部署到生产环境中,并存。
微服务架构
下面来自Microsoft Docs的图片展示了微服务架构风格。
除了微服务本身,微服务架构中还有各种组件。
管理。维护服务的节点。
身份提供程序。在分布式网络中管理身份信息并提供身份验证服务。
服务发现。跟踪服务以及服务地址和终结点。
API 网关。充当客户端的入口点。客户端的唯一联系点,该点反过来会返回底层微服务的响应,有时还会返回多个底层微服务的聚合响应。
CDN。内容分发网络,用于提供静态资源。例如,分布式网络中的页面和 Web 内容。
静态内容。静态资源,如页面和 Web 内容。
微服务独立部署,每个服务都有自己的数据库,因此底层微服务看起来如下面的图片所示
单体架构与微服务架构
单体应用程序更像是一个完整的包,将所有相关的所需组件和服务封装在一个包中。
下图是单体架构的图示表示,无论是完全打包还是基于服务的。
微服务是一种创建小型服务的⽅式,每个服务都在自己的空间中运行,并通过消息传递进⾏通信。这些是直接调⽤自己数据库的独立服务。
下图是微服务架构的图示表示。
在单体架构中,即使采⽤了面向服务的架构⽅式,数据库也依然是所有功能的统⼀数据库,⽽在微服务中,每个服务都会有⾃⼰的数据库。
Docker 容器和 Docker 安装
像 Docker(及其他)这样的容器会划分操作系统资源。例如,网络堆栈、进程命名空间、文件系统层次结构和存储堆栈。Docker 更像是虚拟化操作系统。在此处了解有关 Docker 的更多信息。在此处打开此 URL,然后从 Docker Hub 下载。下载完成后,登录 Docker 并按照说明安装适用于 Windows 的 Docker。
使用 ASP.NET Core 构建微服务
本节将通过图文并茂的方式,一步一步演示如何使用 ASP.NET Core 创建一个 Product 微服务。该服务将使用 ASP.NET Core 2.1 和 Visual Studio 2017 构建。ASP.NET Core 已集成到 VS 2017 中。该服务将拥有自己的 dbcontext 和数据库,以及一个独立的存储库,以便服务可以独立部署。
创建 ASP.NET Core 应用程序解决方案
- 打开 Visual Studio 并添加新项目。
- 选择应用程序为 ASP.NET Core Web 应用程序,并为其命名。
- 接下来,选择 API 作为项目类型,并确保选中“启用 Docker 支持”选项,并将操作系统类型设置为 Linux。
- 解决方案的外观如下所示
添加模型
- 向项目中添加一个名为“Model”的新文件夹。
- 在“Models”文件夹中,添加一个名为 `Product` 的类。
- 为 `Product` 类添加几个属性,例如 `Id`、`Name`、`Description`、`Price`。产品还应具有某种类别,为此,定义了一个类别模型,并在 `product` 模型中添加了 `CategoryId` 属性。
- 同样,添加 `Category` 模型。
启用 EF Core
虽然 .NET Core API 项目内置了对 EF Core 的支持,并且所有相关依赖项都在项目创建和编译时下载,可以在项目中的 SDK 部分找到,如下图所示。
下载的 SDK 中应包含 `Microsoft.EntityFrameworkCore.SqlServer` (2.1.1)。如果不存在,可以通过 Nuget 包显式添加到项目中。
添加 EF Core DbContext
需要一个数据库上下文,以便模型能够与数据库交互。
- 向项目中添加一个名为“DBContexts”的新文件夹。
- 添加一个名为 `ProductContext` 的新类,其中包含 `Products` 和 `Categories` 的 `DbSet` 属性。`OnModelCreating` 是一个可以通过其中将主数据植入数据库的方法。因此,添加 `OnModelCreating` 方法并添加一些示例类别,这些类别将在创建数据库时(最初在 `category` 表中)添加到数据库中。
ProductContext 代码
using Microsoft.EntityFrameworkCore; using ProductMicroservice.Models; namespace ProductMicroservice.DBContexts { public class ProductContext : DbContext { public ProductContext(DbContextOptions<ProductContext> options) : base(options) { } public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Category>().HasData( new Category { Id = 1, Name = "Electronics", Description = "Electronic Items", }, new Category { Id = 2, Name = "Clothes", Description = "Dresses", }, new Category { Id = 3, Name = "Grocery", Description = "Grocery Items", } ); } } }
- 在 `appsettings.json` 文件中添加连接字符串。
打开 `Startup.cs` 文件,添加 EF Core 的 SQL Server DB 提供程序。在 `ConfigureServices` 方法中添加代码 `services.AddDbContext<ProductContext>(o => o.UseSqlServer(Configuration.GetConnectionString("ProductDB")));`。请注意,在 `GetConnectionString` 方法中,传递的是 `appsettings` 文件中添加的连接字符串的键名。
添加存储库
存储库作为微服务的一个微小组件,它封装了数据访问层,有助于数据持久化和可测试性。
- 在项目中添加一个名为“Repository”的新文件夹,并在该文件夹中添加一个名为 `IProductRepository` 的接口。在接口中添加执行 Product 微服务 CRUD 操作的方法。
- 在同一个“Repository”文件夹中添加一个名为 `ProductRepository` 的具体类,该类实现 `IProductRepository`。所有这些方法都需要实现。
- 通过访问上下文方法来实现这些方法。
ProductRepository.cs
using Microsoft.EntityFrameworkCore; using ProductMicroservice.DBContexts; using ProductMicroservice.Models; using System; using System.Collections.Generic; using System.Linq; namespace ProductMicroservice.Repository { public class ProductRepository: IProductRepository { private readonly ProductContext _dbContext; public ProductRepository(ProductContext dbContext) { _dbContext = dbContext; } public void DeleteProduct(int productId) { var product = _dbContext.Products.Find(productId); _dbContext.Products.Remove(product); Save(); } public Product GetProductByID(int productId) { return _dbContext.Products.Find(productId); } public IEnumerable<Product> GetProducts() { return _dbContext.Products.ToList(); } public void InsertProduct(Product product) { _dbContext.Add(product); Save(); } public void Save() { _dbContext.SaveChanges(); } public void UpdateProduct(Product product) { _dbContext.Entry(product).State = EntityState.Modified; Save(); } } }
- 打开项目中的 `Startup` 类,并在 `ConfigureServices` 方法中添加代码 `services.AddTransient<IProductRepository, ProductRepository>()`;以便在需要时在运行时解析存储库的依赖关系。
添加控制器
微服务应该有一个终结点,为此需要一个控制器,该控制器将 HTTP 方法作为服务终结点暴露给客户端。
- 右键单击“Controllers”文件夹,然后添加一个新控制器,如下图所示
- 选择“带读/写操作的 API 控制器”选项来添加控制器。
- 将控制器命名为 `ProductController`。
- 将在“Controllers”文件夹中添加一个 `ProductController` 类,其中包含默认的读/写操作,稍后将被产品读/写操作替换。创建的 HTTP 方法作为服务终结点。
- 可以删除 `ValuesController`,因为它不需要。
- 通过调用存储库方法来为方法添加实现,如下所示。为方便理解概念,此处展示了基本实现。方法可以进行属性路由,并且可以根据需要添加更多注解。
ProductController.cs
using Microsoft.AspNetCore.Mvc;
using ProductMicroservice.Models;
using ProductMicroservice.Repository;
using System;
using System.Collections.Generic;
using System.Transactions;
namespace ProductMicroservice.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
[HttpGet]
public IActionResult Get()
{
var products = _productRepository.GetProducts();
return new OkObjectResult(products);
}
[HttpGet("{id}", Name = "Get")]
public IActionResult Get(int id)
{
var product = _productRepository.GetProductByID(id);
return new OkObjectResult(product);
}
[HttpPost]
public IActionResult Post([FromBody] Product product)
{
using (var scope = new TransactionScope())
{
_productRepository.InsertProduct(product);
scope.Complete();
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}
}
[HttpPut]
public IActionResult Put([FromBody] Product product)
{
if (product != null)
{
using (var scope = new TransactionScope())
{
_productRepository.UpdateProduct(product);
scope.Complete();
return new OkResult();
}
}
return new NoContentResult();
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_productRepository.DeleteProduct(id);
return new OkResult();
}
}
}
实体框架核心迁移
迁移允许我们提供代码来将数据库从一个版本更改到另一个版本。
- 打开程序包管理器控制台。
- 要启用迁移,请键入命令 `Add-Migration`,并为其命名,例如 `InitialCreate`,然后按 Enter。
- 命令执行后,如果查看我们的解决方案,会发现一个新创建的 `Migrations` 文件夹。其中包含两个文件。一个是当前上下文模型的快照。请随意查看这些文件。这些文件非常直观。
- 为了确保迁移已应用于数据库,还有一个命令。它称为 `update-database` 命令。如果执行,迁移将应用于当前数据库。
- 检查 SQL Server Management Studio 以验证数据库是否已创建。
- 当查看 `Categories` 表的数据时,显示了三个类别的默认主数据。
运行 Product 微服务
可以通过 IIS Express 运行服务。例如,Visual Studio 默认设置,或者通过 Docker 容器。
通过 IIS Express
在 Visual Studio 中选择“IIS Express”,如下图所示,然后按“F5”,或直接单击 IIS Express 按钮。
应用程序启动后,浏览器页面将打开。由于没有任何显示内容,它将是空白的,但可以通过任何 API 测试客户端来测试服务。这里使用 Postman 测试服务终结点。保持打开状态并运行应用程序。
如果机器上没有 Postman,请安装它并启动。
POST
要测试 `POST` 方法(即创建新资源),请在 Postman 中选择方法为 `POST`,然后提供终结点(即 https://:44312/api/product)。然后在“Body”部分,添加一个具有与 `Product` 模型相似属性的 JSON,如下图所示,然后单击“Send”。
响应会返回产品的 Id。
控制器中的 `Post` 方法负责在数据库中创建资源并发送响应。
代码 `return` `CreatedAtAction(nameof(Get), new { id = product.Id }, product);` 返回已创建资源的地址,可以在响应的“Headers”选项卡下的 `Location` 属性中进行检查。
对 `product` 表执行 `select` 查询,并显示新创建的产品添加的行。
以类似的方式再创建另一个 `product`。
GET
现在使用相同的地址执行 `GET` 请求,将显示两条记录作为 JSON 结果响应。
删除
通过选择 `DELETE` 作为动词,并将 `id` 附加为 `1`(如果需要删除 id 为 1 的产品),然后按“Send”来执行 `delete` 请求。
在数据库中,id 为 1 的一条记录被删除。
PUT
PUT 动词负责更新资源。选择 `PUT` 动词,提供 API 地址,并在“Body”部分以 JSON 格式提供需要更新的产品详细信息。例如,更新 id 为 2 的产品,并将其名称、描述和价格从 Samsung 更新为 iPhone。按“Send”。
检查数据库以查看已更新的产品。
通过 Docker 容器
可以在 Docker 命令提示符下运行 Docker 命令来运行服务,也可以使用 Visual Studio。由于我们添加了 Docker 支持,因此可以使用 Visual Studio 在 Docker 容器中轻松运行服务。
- 向解决方案添加容器编排器支持,如下图所示
- 这将要求选择编排器。选择 Docker Compose,然后按 OK。
添加到解决方案后,解决方案的外观如下所示,包含 `docker-compose`、`dockerignore` 以及 `docker-compose.yml` 及其覆盖文件。
保存解决方案后,它会在容器下构建项目并创建 Docker 镜像。所有命令的执行情况都可以在保存解决方案时在输出窗口中看到。
- 以管理员模式打开命令提示符,并导航到项目文件所在的相同文件夹。
- 运行 `docker images` 命令以查看所有已创建的镜像。我们看到 `productmicroserviceimage` 是最新的。
- 现在,使用 Docker 作为选项运行应用程序,如下图所示
- 现在,运行 `docker ps` 命令以查看正在运行的容器。它显示容器在 32773:80 端口上运行。
- 由于容器正在运行,因此最好测试一下现在在容器下运行的服务。要测试服务,请在地址中将“
values
”替换为 `product`,如下图所示。理想情况下,它应该获取产品详细信息。但它会抛出异常,如下图所示。 - 在 IIS Express 下运行相同的内容可以正常工作,即在 44312 端口上。将“
values
”替换为 `product` 以获取 `product` 详细信息。 - 由于在 IIS Express 中,应用程序运行正常,而在 Docker 容器中则不行,并且错误清楚地表明 SQL Server 存在问题,因为它无法识别我们的 Docker 容器(或者它没有在我们的 Docker 容器下运行)。在这种情况下,Docker 容器作为主机计算机内的独立计算机运行。因此,要连接到主机计算机上的 SQL 数据库,需要启用对 SQL 的远程连接。我们可以解决这个问题。
- 打开 SQL Server Configuration Manager。现在选择 MSSQLSERVER 的协议,然后在 TCP/IP 部分获取 IPAll 端口号。
- `appsettings.json` 文件中提到的连接字符串将数据源指向 local,Docker 容器无法识别。它需要正确的 IP 地址、端口和 SQL 身份验证。因此,提供相关详细信息,即数据源为 IP 地址、端口号和 SQL 身份验证详细信息,如下图所示。
- 现在,像以前一样,使用 Docker 作为选项再次运行应用程序。
这次,收到了响应。
- 在 Postman 中再次测试。
- 使用 IIS Express URL 再次测试。
这证明了微服务在两个终结点和两个操作系统上独立本地部署运行。
结论
微服务是围绕特定业务能力构建的服务,它可以独立部署,称为有界上下文。本文关于微服务的文章重点介绍了微服务是什么,以及它们相比单体服务架构的优势。文章描述了如何使用 ASP.NET Core 开发微服务,并通过 IIS 和 Docker 容器运行它。同样,该服务可以拥有多个镜像,并且可以同时在多个容器上运行。