使用 ASP.NET Core 3.1 的微服務開發指南

溪源More發表於2021-10-30

使用 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 閘道器有很多好處。

相關文章