如何使用Dapr在.NET微服务中实现服务调用与状态管理功能插图

Dapr实战:在.NET微服务中轻松搞定服务调用与状态管理

大家好,作为一名常年和微服务“缠斗”的老兵,我深知服务间通信和状态管理的复杂性。你是否也曾为配置HTTP客户端、处理重试熔断、或是为无状态服务寻找一个可靠的状态存储而头疼?直到我遇见了Dapr(Distributed Application Runtime),它像一位贴心的管家,把这些分布式系统的难题都封装成了简单的构建块(Building Blocks)。今天,我就带大家手把手,在.NET微服务中实战Dapr最核心的两个功能:服务调用(Service Invocation)和状态管理(State Management)。你会发现,事情原来可以这么简单。

一、环境准备与项目搭建

首先,我们需要一个战场。确保你的机器上已经安装了Dapr CLI.NET SDK。然后,通过CLI初始化Dapr环境:

dapr init

这个命令会安装Dapr运行时(默认包括Redis,用于状态管理和发布订阅)到你的本地环境。完成后,用 dapr --version 检查一下。

接下来,创建我们的示例项目。我们将模拟一个简单的电商场景:一个 `OrderService`(订单服务)需要调用 `ProductService`(产品服务)来获取产品详情,同时管理自己的订单状态。

# 创建两个Web API项目
dotnet new webapi -n ProductService
dotnet new webapi -n OrderService

# 进入项目目录,添加Dapr对ASP.NET Core的支持
cd ProductService
dotnet add package Dapr.AspNetCore
cd ../OrderService
dotnet add package Dapr.AspNetCore

踩坑提示:Dapr的包版本更新较快,建议查看官方GitHub仓库以获取最新的稳定版本号进行安装。

二、实现服务调用(Service Invocation)

服务调用构建块让服务间通信变得像调用本地方法一样简单,它为我们处理了服务发现、重试、加密等所有传输层面的问题。

第一步:改造ProductService(服务提供方)

这几乎不需要任何特殊改造!Dapr通过sidecar模式工作,你的服务本身就是一个普通的Web API。我们只需添加一个控制器:

// ProductService/Controllers/ProductController.cs
using Microsoft.AspNetCore.Mvc;

namespace ProductService.Controllers;

[ApiController]
[Route("[controller]")]
public class ProductController : ControllerBase
{
    private static readonly Dictionary Products = new()
    {
        {1, new Product(1, "极客咖啡杯", 99.50m)},
        {2, new Product(2, "源码库联名键盘", 450.00m)}
    };

    [HttpGet("{id}")]
    public ActionResult Get(int id)
    {
        // 模拟一点延迟,方便观察
        Thread.Sleep(200);
        if (Products.TryGetValue(id, out var product))
            return Ok(product);
        return NotFound();
    }

    public record Product(int Id, string Name, decimal Price);
}

关键点:你的服务保持完全的标准ASP.NET Core形态,这就是Dapr的“非侵入性”魅力。

第二步:在OrderService中调用ProductService(服务调用方)

这里我们使用Dapr SDK提供的 DaprClient。首先在 Program.cs 中注册它:

// OrderService/Program.cs
builder.Services.AddControllers().AddDapr(); // 添加Dapr支持
builder.Services.AddDaprClient(); // 注册DaprClient

然后,创建一个控制器来发起调用:

// OrderService/Controllers/OrderController.cs
using Dapr.Client;
using Microsoft.AspNetCore.Mvc;

namespace OrderService.Controllers;

[ApiController]
[Route("[controller]")]
public class OrderController : ControllerBase
{
    private readonly DaprClient _daprClient;
    private readonly ILogger _logger;

    public OrderController(DaprClient daprClient, ILogger logger)
    {
        _daprClient = daprClient;
        _logger = logger;
    }

    [HttpPost]
    public async Task<ActionResult> CreateOrder([FromBody] CreateOrderCommand command)
    {
        // 1. 通过Dapr调用ProductService,获取产品信息
        // 格式:http://localhost:/v1.0/invoke//method/
        // SDK帮我们封装了这一切
        var product = await _daprClient.InvokeMethodAsync(
            HttpMethod.Get,
            "productservice", // 目标服务的Dapr应用ID
            $"product/{command.ProductId}");

        _logger.LogInformation($"获取到产品: {product.Name}, 价格: {product.Price}");

        // 2. 创建订单对象(状态保存将在下一节实现)
        var order = new Order(Guid.NewGuid(), product.Id, product.Name, product.Price, command.Quantity, DateTime.UtcNow);
        return Ok(order);
    }

    public record CreateOrderCommand(int ProductId, int Quantity);
    public record Product(int Id, string Name, decimal Price);
    public record Order(Guid Id, int ProductId, string ProductName, decimal UnitPrice, int Quantity, DateTime CreatedTime);
}

看,调用另一个服务只需要一行 InvokeMethodAsync!你不需要知道它的具体IP和端口,只需要知道它在Dapr中注册的 app-id(这里是 `productservice`)和API路径。

三、实现状态管理(State Management)

现在,我们需要把创建的订单保存起来。Dapr的状态管理支持多种存储(Redis、CosmosDB、MySQL等),我们使用安装Dapr时自带的Redis。

第一步:在OrderService中保存状态

修改上面的 CreateOrder 方法,在创建订单对象后,将其保存到Dapr状态存储:

// 在OrderController的CreateOrder方法中,return之前添加:
// 3. 使用Dapr保存订单状态
// store-name 对应 components/statestore.yaml 中定义的名称
await _daprClient.SaveStateAsync("statestore", order.Id.ToString(), order);

_logger.LogInformation($"订单已保存,ID: {order.Id}");
return Ok(order);

第二步:添加获取订单状态的接口

[HttpGet("{orderId}")]
public async Task<ActionResult> GetOrder(Guid orderId)
{
    // 从状态存储中读取订单
    var state = await _daprClient.GetStateAsync("statestore", orderId.ToString());
    if (state == null)
        return NotFound();
    return Ok(state);
}

第三步:理解状态存储组件配置

Dapr通过组件YAML文件进行配置。在运行服务时,我们需要指定一个包含组件定义的目录。通常可以在解决方案根目录创建一个 `components` 文件夹,里面放置一个 `statestore.yaml` 文件:

# components/statestore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore # 这个名字就是上面代码中使用的“store-name”
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379 # Dapr init安装的Redis默认地址
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"

这个文件告诉Dapr:“当使用名为 `statestore` 的存储时,请连接到本地的Redis。”在生产环境中,你只需要修改这个YAML文件中的连接信息,代码一行都不用动!

四、运行与测试

激动人心的时刻到了!我们需要用Dapr来运行这两个服务。

第一步:运行ProductService

# 在ProductService项目目录下
dapr run --app-id productservice --app-port 5001 --dapr-http-port 3501 --components-path ../components dotnet run

第二步:运行OrderService

# 在OrderService项目目录下
dapr run --app-id orderservice --app-port 5002 --dapr-http-port 3502 --components-path ../components dotnet run

参数解释
--app-id:服务在Dapr中的唯一标识,用于服务发现。
--app-port:应用本身监听的端口。
--dapr-http-port:Dapr sidecar的HTTP端口,我们的服务通过这个端口与sidecar通信。
--components-path:指向我们刚才创建的组件配置目录。

第三步:测试API

打开Postman或使用curl:

# 1. 创建订单,调用OrderService(通过Dapr sidecar端口3502)
curl -X POST http://localhost:3502/v1.0/invoke/orderservice/method/order 
  -H "Content-Type: application/json" 
  -d '{"productId": 1, "quantity": 2}'

# 你会收到一个包含订单ID的响应。

# 2. 通过订单ID查询状态
# 将 {your-order-id} 替换为上一步返回的ID
curl http://localhost:3502/v1.0/invoke/orderservice/method/order/{your-order-id}

你也可以直接通过应用端口(5002)调用,但通过Dapr端口(3502)调用才能真正体验到Dapr的完整功能链。

五、总结与思考

通过这个简单的实战,我们看到了Dapr如何极大地简化了.NET微服务的开发:
1. 服务调用:无需关心服务发现和通信细节,用应用ID直接调用。
2. 状态管理:用统一的API操作状态,底层存储可插拔,代码与基础设施解耦。

更重要的是,Dapr的sidecar模式让你的应用代码保持了干净和可移植性。今天我们在本地用Redis,明天要上云换成Azure Cosmos DB,只需要更新那个YAML组件文件,然后重启服务即可,业务代码无需任何修改

当然,这只是Dapr的冰山一角。它还有发布订阅、事件绑定、可观测性、Actor模式等强大功能。在微服务架构的路上,Dapr无疑是一个值得你放入工具箱的利器。希望这篇教程能帮你顺利起步,少踩一些我当年踩过的坑。Happy coding!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。