使用 ASP.NET Core 3.1 的微服务 – 终极详细指南
https://procodeguide.com/programming/microservices-asp-net-core/
ASP.NET Core 微服务是一种架构,其中应用程序被创建为多个小的独立的可服务组件。本文将详细介绍如何使用 ASP.NET Core、Serilog、Swagger UI、健康检查和 Docker 容器创建微服务。
目录
微服务架构
单体 vs微服务
为什么使用 ASP.NET Core 微服务
使用 ASP.NET Core 实现微服务
使用 CRUD 操作创建服务
创建 ASP.NET Core 项目
实现订单服务
添加 Web API 版本控制
使用 URL 实现 Web API 版本控制
将日志添加到微服务
使用 Serilog 实现日志记录
添加服务监控机制
使用 ASP.NET Core Healthchecks 实现微服务监控
使用 ASP.NET Core 为微服务创建文档
使用 Swashbuckle Swagger 实现文档
将容器化添加到微服务
使用 Docker 实现容器化
微服务的好处
微服务的缺点
最佳实践
概括
源代码下载
微服务架构
微服务架构风格如上图所示。微服务架构是一种将一个大型应用程序开发为一组小型独立服务的风格。在这里,每个服务都实现了特定的功能并拥有自己的数据存储。每个服务功能都应该小到足以实现一个用例,而大到足以提供一些价值。每个服务都应该单独部署,以便可以独立扩展。这些服务应尽可能相互独立,如果需要进行服务间通信,则可以使用一些轻量级的通信协议。
身份提供者用于向应用程序提供用户身份验证服务。
要了解有关身份提供程序的详细信息以及如何保护基于 ASP.NET Core 的应用程序的安全,您可以查看我关于*ASP.NET Core 安全性的*系列
API 网关是所有请求的单一入口点,有助于管理端点并协调不同的服务。
容器是一个标准的软件单元,它捆绑了应用程序或功能及其所有依赖项,以便可以在任何具有容器主机的新系统上快速可靠地部署应用程序。
容器编排是一种用于管理大型应用程序中容器生命周期的软件,这有助于根据负载扩展应用程序。
微服务是其在容器与它相依一起捆绑的实际独立的小服务
Data Store用于存储微服务数据,基本原理是每个服务管理自己的数据。
单体 vs 微服务
整体式 | 微服务 |
---|---|
单个服务/应用程序应包含所有业务功能 | 单个服务应该只包含一个业务功能 |
所有服务都是紧耦合的 | 所有服务都是松耦合的 |
应用程序是用一种单一的编程语言开发的 | 每个服务可以使用不同的编程语言 |
所有服务的单一数据库。 | 每个服务都有独立的数据库 |
所有服务都需要一起部署在VM上 | 每个服务都可以部署在单独的虚拟机上 |
所有服务都在同一进程中运行,因此如果一项服务出现故障,则整个应用程序都会中断 | 每个服务在不同的进程中运行,因此一个服务的失败不会影响其他服务 |
难以扩展特定服务,因为新实例必须拥有所有服务 | 可以轻松扩展,因为任何单个服务都可以独立部署 |
单个大型团队处理整个应用程序 | 每个服务的独立小团队工作更集中。 |
易于开发和测试小型应用程序 | 由于它是一个分布式系统,因此增加了应用程序的复杂性 |
为什么使用 ASP.NET Core 微服务?
.NET Core 提供以下优势,适用于使用 ASP.NET Core 的微服务
- 从头开始构建的轻量级框架
- 跨平台支持
- 针对容器化进行了优化
使用 ASP.NET Core 实现微服务
这是关于使用 ASP.NET Core 实现微服务的简短视频(.NET Core 微服务示例),在这里,我们将详细介绍使用 ASP.NET Core 创建微服务的分步过程。我们将创建一个订单服务,该服务将提供端点以在应用程序中添加、取消、按 ID 获取订单和按客户 ID 获取订单。本演示已在 Visual Studio 2019 版本 16.6.2 中执行
使用 CRUD 操作创建服务
创建 ASP.NET Core 项目
对于带有 ASP.NET Core 演示的微服务,我们将创建一个 ASP.NET Core 3.1 Web API 项目来演示 .net Core Web API 微服务。
实现订单服务
为了使用 ASP.NET Core 演示微服务,我们将创建订单微服务,该服务将包含仅与订单相关的功能。这是微服务的基本要求,它应该只实现一个功能,因此我们的微服务将只包含与订单相关的功能。我们将实现以下接口功能
- 添加 – 创建新订单
- 取消 – 取消现有订单
- GetById – 按 ID 获取订单
- GetByCustomerId – 获取客户 ID 的所有订单
下面我们将快速为订单添加实体模型并为订单微服务启用实体框架核心。
如果您需要有关实体框架如何工作的更多详细信息,请查看我关于*ASP.NET Core 3.1 中的 Entity Framework Core 的*另一篇文章
添加模型
添加订单实体类
public class Order
{
public string Id { get; set; }
public string ProductId { get; set; }
public double Cost { get; set; }
public DateTime Placed { get; set; }
public string CustomerId { get; set; }
public string Status { get; set; }
}
使用实体框架核心向 API 添加 CRUD 操作
我们将利用 Entity Framework Core 来实现订单服务的数据库操作。
为实体框架核心安装所需的包
Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
添加数据库上下文类
这是与给定模型类的实体框架功能协调的主要类。
public interface IApplicationDbContext
{
DbSet<Order> Orders { get; set; }
Task<int> SaveChanges();
}
public class ApplicationDbContext : DbContext, IApplicationDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public new async Task<int> SaveChanges()
{
return await base.SaveChangesAsync();
}
}
添加订单仓库
存储库是一个组件,它封装了与数据存储和对它们执行的操作相关的对象。DbContext 使用依赖注入作为构造函数中的参数传递。
public interface IOrderRepository
{
Task<string> Add(Order order);
Task<Order> GetById(string id);
Task<string> Cancel(string id);
Task<Order> GetByCustomerId(string custid);
}
public class OrderRepository : IOrderRepository
{
private IApplicationDbContext _dbcontext;
public OrderRepository(IApplicationDbContext dbcontext)
{
_dbcontext = dbcontext;
}
public async Task<string> Add(Order order)
{
_dbcontext.Orders.Add(order);
await _dbcontext.SaveChanges();
return order.Id;
}
public async Task<string> Cancel(string id)
{
var orderupt = await _dbcontext.Orders.Where(orderdet => orderdet.Id == id).FirstOrDefaultAsync();
if (orderupt == null) return "Order does not exists";
orderupt.Status = "Cancelled";
await _dbcontext.SaveChanges();
return "Order Cancelled Successfully";
}
public async Task<Order> GetByCustomerId(string custid)
{
var order = await _dbcontext.Orders.Where(orderdet => orderdet.CustomerId == custid).FirstOrDefaultAsync();
return order;
}
public async Task<Order> GetById(string id)
{
var order = await _dbcontext.Orders.Where(orderdet => orderdet.Id == id).FirstOrDefaultAsync();
return order;
}
}
将应用程序连接到数据库
在 appsettings.json 文件中指定 SQL Server 连接字符串。
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\mssqllocaldb;Database=OrderDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
在启动类中注册服务
您需要在启动类中的 ConfigureServices 方法中将数据库上下文和订单存储库配置为服务
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
ef => ef.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
services.AddTransient<IOrderRepository, OrderRepository>(); services.AddControllers();
//Remaining code has been removed
}
添加迁移
要自动化迁移并创建数据库,我们需要在包管理器控制台中运行以下命令。
add-migration InitialMigration
update-database
这里要注意的一件事是,该表仅为订单详细信息创建。产品和客户表不是使用外键引用创建的,因为您必须保持微服务小且专注于一个单一的功能。产品和客户将在他们自己的单独的数据库中,并拥有自己的微服务实现。如果您需要将产品详细信息或客户详细信息显示为订单详细信息的一部分,那么您需要调用相应的微服务并获取所需的详细信息。
添加订单控制器
这是订单控制器的代码,已添加以公开订单微服务的端点。订单存储库已使用依赖注入作为构造函数参数传递
[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
private IOrderRepository _orderRepository;
public OrderController(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
[HttpPost]
[Route("Add")]
public async Task<ActionResult> Add([FromBody] Order orderdet)
{
string orderid = await _orderRepository.Add(orderdet);
return Ok(orderid);
}
[HttpGet]
[Route("GetByCustomerId/{id}")]
public async Task<ActionResult> GetByCustomerId(string id)
{
var orders = await _orderRepository.GetByCustomerId(id);
return Ok(orders);
}
[HttpGet]
[Route("GetById/{id}")]
public async Task<ActionResult> GetById(string id)
{
var orderdet = await _orderRepository.GetById(id);
return Ok(orderdet);
}
[HttpDelete]
[Route("Cancel/{id}")]
public async Task<IActionResult> Cancel(string id)
{
string resp = await _orderRepository.Cancel(id);
return Ok(resp);
}
}
我们的订单服务已准备好执行操作。但要使其成为微服务,我们必须启用日志记录、异常处理、文档、监控、容器化等功能。让我们看看如何使用 ASP.NET Core 为微服务实现这些功能
添加 Web API 版本控制
微服务应该以这样一种方式构建,即在不破坏现有客户端的情况下易于更改,并且还应该能够并行支持多个版本,因此 Web API 版本控制将帮助我们实现这一点。
带有 ASP.NET Core 的微服务支持 Web API 版本控制,使用该功能我们可以实现同一 API 的多个版本,以便不同的客户端可以使用所需版本的 API。
使用 URL 实现 Web API 版本控制
在使用 URL 的 Web API 版本控制中,版本号是 URL 的一部分,即 http://server:port/api/v1/order/add
安装 Web API 版本控制包
Install-Package Microsoft.AspNetCore.Mvc.Versioning
在启动类中配置 Web API 版本控制
在 startup.cs 文件的 ConfigureServices 方法中启用对 Web API 版本控制的支持。
public void ConfigureServices(IServiceCollection services){ services.AddApiVersioning(apiVerConfig => { apiVerConfig.AssumeDefaultVersionWhenUnspecified = true; apiVerConfig.DefaultApiVersion = new ApiVersion(new DateTime(2020, 6, 6)); }); //Remaining code has been removed }
将 URL Web API 版本控制添加到订单控制器
您需要在路由属性中添加参数 v{version:apiVersion} 如 R oute(“api/v{version:apiVersion}/[controller]”)以便 API 版本成为 URL 的一部分。
[ApiVersion("1.0")][Route("api/v{version:apiVersion}/[controller]")][ApiController]public class OrderController : ControllerBase{//Remaining code has been removed}
如果您需要有关 ASP.NET Core 中 Web API 版本控制的更多详细信息,请查看我关于*ASP.NET Core 3.1 中的 Web API 版本控制的*另一篇文章
将日志添加到微服务
部署到生产环境后,跟踪和分析问题应该很容易。日志帮助我们分析有时可能难以模拟的复杂问题。总是需要对需要日志进行分析的应用程序问题进行故障排除。对于带有 ASP.NET Core 的微服务,我们将使用 Serilog 来记录应用程序的详细信息。
使用 Serilog 实现日志记录
有许多第三方组件,其中之一是*Serilog*。Serilog 是一种流行的第三方日志记录提供程序,在 ASP.NET Core 日志记录中受支持。
关于在 ASP.NET Core 中实现 Serilog 的详细解释,可以参考我的另一篇文章*ASP.NET Core Logging with Serilog*
安装所需的 Serilog 包
Install-Package Serilog.AspNetCoreInstall-Package Serilog.Extensions.LoggingInstall-Package Serilog.Extensions.HostingInstall-Package Serilog.Sinks.RollingFileInstall-Package Serilog.Sinks.Async
添加 Serilog 配置
Serilog RollingFile Sink 是通过在 appsettings.json 文件中添加配置来实现的。可以通过在名为 MinimumLevel 的属性中设置日志级别值来指定日志级别。
"Serilog": { "MinimumLevel": "Information", "WriteTo": [ { "Name": "Async", "Args": { "configure": [ { "Name": "RollingFile", "Args": { "pathFormat": "Serilogs\AppLogs-{Date}.log", "outputTemplate": "{Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}", "fileSizeLimitBytes": 10485760 } } ] } } ] }
在 Program & Startup 类中配置 Serilog
将 UseSerilog() 添加到 Program.cs 中的 CreateDefaultBuilder
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
从 Startup.cs 中的 appsettings.json 文件加载 Serilog 配置
public Startup(IConfiguration configuration){ Configuration = configuration; Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .CreateLogger();}
在订单控制器中添加日志记录
Serilog.Log 类已用于添加带有 Debug 方法的日志,以便在出现某些异常时进行调试和错误。
[HttpPost][Route("Add")]public async Task<ActionResult> Add([FromBody] Order orderdet){ try { Log.Debug("Order Addition Started"); Log.Debug("Order Addition Input", orderdet); string orderid = await _orderRepository.Add(orderdet); Log.Debug("Order Addition Output", orderid); return Ok(orderid); } catch (Exception ex) { Log.Error("Order Addition Failed", ex); throw new Exception("Order Addition Failed", innerException: ex); }}//Remaining code has been removed
出于演示目的,我仅在控制器操作“添加”中添加了日志记录功能,但作为一种良好的做法,您需要在具有适当日志级别的完整应用程序中添加日志,这有助于调试生产中的复杂问题。由于这是微服务,异步日志写入已配置为通过将工作委托给后台线程来减少日志记录调用的开销。
另外,这里要注意的另一件事是,如果您在 docker 容器中运行 ASP.NET 核心应用程序,那么您需要小心日志文件的位置,就好像您将日志文件存储在同一个容器中一样,则有可能丢失那个数据。在容器化环境中,日志应该存储在某个持久卷上。
添加健康检查机制
检查我们的服务是否已启动并运行或正常运行总是很好的。在我们的客户通知我们我们损坏的服务之前,我们应该能够主动识别我们损坏的服务并采取纠正措施。Healthchecks 允许我们检查服务是否健康,即启动和运行。Healthcheck 端点也可用于从负载均衡器检查其状态,如果我们的服务返回该服务器上的运行状况检查失败,则禁用负载均衡器上的服务器。对于带有 ASP.NET Core 的微服务,我们将使用 ASP.NET Core HealthChecks 进行监控。
使用 ASP.NET Core Healthchecks 实现微服务监控
Healthchecks 是 ASP.NET Core 中的内置中间件,用于报告应用程序的运行状况。健康检查可以作为应用程序中的另一个端点公开。
安装HealthChecks包
使用包管理器控制台安装健康检查所需的包
Install-Package Microsoft.Extensions.Diagnostics.HealthChecksInstall-Package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore
在 Startup 类中配置健康检查
在这里,我们配置了基本应用程序健康检查和实体框架数据库上下文健康检查,以确认应用程序可以与为实体框架核心 DbContext 配置的数据库进行通信
public class Startup{ public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks() .AddDbContextCheck<ApplicationDbContext>(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseHealthChecks("/checkhealth"); } //Remaining code has been removed}
您可以使用 URL http://serverip:port/checkhealth 检查服务健康状况
使用 ASP.NET Core 为微服务创建文档
维护微服务的更新文档总是好的。其他团队应该能够参考这些 API 规范并相应地使用微服务。我们将实施 Swashbuckle.AspNetCore 来为订单微服务生成 Swagger 文档。
使用 Swashbuckle Swagger 实现文档
Swashbuckle 是一个开源库,用于为 ASP.NET Core Web API 生成 swagger 文档。本文档可用于探索和测试 API。
安装所需的 swashbuckle swagger 包
使用包管理器控制台安装 swashbuckle 所需的包
Install-Package Swashbuckle.AspNetCoreInstall-Package Microsoft.OpenApi
配置 swagger 启动类
public class Startup{ public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Microservice - Order Web API", Version = "v1", Description = "Sample microservice for order", }); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseSwagger(); app.UseSwaggerUI(options => options.SwaggerEndpoint("/swagger/v1/swagger.json", "PlaceInfo Services")); } //Remaining code has been removed}
以下是使用 swagger 为订单微服务生成的文档
将容器化添加到微服务
容器化用于捆绑应用程序或应用程序的功能、所有依赖项及其在容器映像中的配置。此映像部署在主机操作系统上,捆绑的应用程序作为一个单元工作。容器镜像的概念允许我们在几乎不做任何修改的情况下跨环境部署这些镜像。通过这种方式,可以轻松快速地扩展微服务,因为新容器可以轻松部署用于短期目的。
Docker 将用于向我们的微服务添加容器化。Docker 是一个开源项目,用于创建可以在云端或本地的 docker 主机上运行的容器。
使用 Docker 实现容器化
这是一个快速的 .NET 核心微服务 docker 教程。要在解决方案资源管理器中的项目上启用 ASP.NET Core 项目中的 Docker 支持,然后从菜单中选择 Add=>Docker Support
这将启用 docker 并为项目创建一个 Dockerfile,如下所示
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
#Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed.
#For more information, please see https://aka.ms/containercompat
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-nanoserver-sac2016 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-nanoserver-sac2016 AS build
WORKDIR /src
COPY ["ProCodeGuide.Sample.Microservice/ProCodeGuide.Sample.Microservice.csproj", "ProCodeGuide.Sample.Microservice/"]
RUN dotnet restore "ProCodeGuide.Sample.Microservice/ProCodeGuide.Sample.Microservice.csproj"
COPY . .
WORKDIR "/src/ProCodeGuide.Sample.Microservice"
RUN dotnet build "ProCodeGuide.Sample.Microservice.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "ProCodeGuide.Sample.Microservice.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProCodeGuide.Sample.Microservice.dll"]
这使得应用程序在 Docker 主机上的容器中运行。为此,可以在 Windows 机器上安装*docker 桌面*。
微服务的好处
- 温和的学习曲线 ——当业务功能被分解成小服务时,它允许开发人员只学习该服务的功能。
- 更好的扩展——每个服务都是独立部署的,因此可以单独扩展。
- 更短的开发时间——应用程序开发周期可以更短,因为每个服务组件都是相互独立的。
- 故障隔离 ——一项服务的故障不影响其他服务的运行。
- 依赖项更简单 ——不依赖于单一的编程语言和部署模型。
- 由于不同的团队正在开发不同的服务,因此可以并行且快速地开发不同的服务。
微服务的缺点
- 小型独立服务需要相互协调,与单体应用相比可能并不简单
- 跨多个服务管理分布式事务可能很复杂。
- 有多个服务/组件需要监控。
- 由于每个独立的服务都需要在集成测试之前进行测试,因此测试可能会花费很少的时间。
- 大型应用程序的整个部署架构变得非常难以管理。
微服务架构是关于更好地处理大型复杂系统,但要实现这一点,它会暴露自己的一系列复杂性和实现挑战。尽管存在缺点或问题,但采用微服务的好处是许多公司实施微服务的驱动因素。
最佳实践
- 微服务应该只实现一个应该能够交付价值的单一功能。
- 每个微服务都应该有自己的数据存储,这不应该在服务之间共享。
- 始终维护微服务的更新文档。
- 使用容器来部署服务。
- DevOps 和 CI/CD 实践
概括
我们介绍了什么是微服务架构以及如何开始使用 ASP.NET Core 3.1 的微服务。我还没有介绍微服务的一项更重要的功能,即自动化测试。自动化单元测试本身就是一个非常庞大的话题,我会单独写一篇文章来讨论它。
快速回顾具有微服务特性的 .Net Core
- 微服务是一种架构,其中应用程序被创建为多个小的独立的可服务组件
- 微服务应该只包含单一的业务功能,并且应该足够小以保持专注,并且足够大以交付价值。
- 每个微服务都应该有自己的数据存储。
- 微服务可以使用轻量级协议相互通信,即通过 HTTP 或高级消息队列协议 (AMQP)
- 微服务可以独立部署在单独的虚拟机上,可以独立扩展。
- 为大型和复杂的基于微服务的应用程序实现 API 网关有很多好处。