Dapr 運用
- 前置條件
- Docker
- Win10
Dapr 部署
本文將采用本地部署的方式。
安裝 Dapr CLI
打開 Windows PowerShell 或 cmd ,運行以下命令以安裝 Dapr CLI
,並添加安裝路徑到系統環境變量中。
powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
這里安裝可能會失敗。如果失敗可以手動安裝。
- 打開 Dapr 發布頁面下載
dapr_windows_amd64.zip
- 解壓文件 zip 文件
- 把解壓后的文件拷貝到
C:\dapr
中
安裝 MySql
Docker 啟動 Mysql
docker run --name mysqltest -e MYSQL_ROOT_PASSWORD=123456 -d mysql
使用 Dapr CLI 安裝 Darp runtime
在 Windows PowerShell 或 cmd 中使用命令 dapr init
以安裝 Dapr。
同時可以在 Docker 中查看 Dapr 容器。
至此,一個本地 Dapr 服務搭建完成。
使用 Asp.Net Core
搭建 ProductService 服務
ProductService 提供兩個服務
- 獲取所有產品集合
- 添加產品
-
使用
ASP.Net Core
創建 ProductService ,具體參考源碼 -
Dapr 啟動 ProductService
dapr run --app-id productService --app-port 5000 dotnet run
-
獲取所有產品集合,使用 curl 命令
curl -X GET http://localhost:5000/getlist
或者
curl -X GET http://localhost:54680/v1.0/invoke/productService/method/getlist
-
添加一個產品
curl -X POST https://localhost:5001/product -H "Content-Type: application/json" -d "{ \"id\": \"14a3611d-1561-455f-9c72-381eed2f6ee3\" }"
-
重點,通過 Dapr 添加一個產品,先看添加產品的代碼
/// <summary> /// 創建產品 /// </summary> /// <param name="productCreate">產品創建模型</param> /// <returns></returns> [Topic("product")] [HttpPost("product")] public async Task<bool> CreateProduct(ProductCreate productCreate) { _productContext.Products.Add(new Product { ProductID = productCreate.ID }); return await _productContext.SaveChangesAsync() == 1; }
-
使用 Dapr cli 發布事件
dapr invoke -a productService -m product -p "{\"id\":\"b1ccf14a-408a-428e-b0f0-06b97cbe4135\"}"
輸出為:
true App invoked successfully
-
使用 curl 命令直接請求 ProductService 地址
curl -X POST http://localhost:5000/product -H "Content-Type: application/json" -d "{ \"id\": \"14a3611d-1561-455f-9c72-381eed2f64e3\" }"
輸出為:
true
-
使用 curl 命令通過 Dapr runtime
curl -X POST http://localhost:54680/v1.0/invoke/productService/method/product -H "Content-Type: application/json" -d "{ \"id\": \"14a3611d-1561-455f-9c72-381eed2f54e3\" }"
輸出為:
true
-
注意:
- Dapr 使用 App 端口號應與服務端口號相同,例如:
ASP.Net Core
服務端口號為5000,則在使用 Dapr 托管應用程序時的端口號也應使用 5000
至此, ProductService 創建完成。
使用 Golang 創建 gRPC Server
-
創建 Server
package main import ( "context" "fmt" "log" "net" "github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/empty" pb "github.com/dapr/go-sdk/daprclient" "google.golang.org/grpc" ) // server is our user app type server struct { } func main() { // create listiner lis, err := net.Listen("tcp", ":4000") if err != nil { log.Fatalf("failed to listen: %v", err) } // create grpc server s := grpc.NewServer() pb.RegisterDaprClientServer(s, &server{}) fmt.Println("Client starting...") // and start... if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } // Sample method to invoke func (s *server) MyMethod() string { return "Hi there!" } // This method gets invoked when a remote service has called the app through Dapr // The payload carries a Method to identify the method, a set of metadata properties and an optional payload func (s *server) OnInvoke(ctx context.Context, in *pb.InvokeEnvelope) (*any.Any, error) { var response string fmt.Println(fmt.Sprintf("Got invoked with: %s", string(in.Data.Value))) switch in.Method { case "MyMethod": response = s.MyMethod() } return &any.Any{ Value: []byte(response), }, nil } // Dapr will call this method to get the list of topics the app wants to subscribe to. In this example, we are telling Dapr // To subscribe to a topic named TopicA func (s *server) GetTopicSubscriptions(ctx context.Context, in *empty.Empty) (*pb.GetTopicSubscriptionsEnvelope, error) { return &pb.GetTopicSubscriptionsEnvelope{ Topics: []string{"TopicA"}, }, nil } // Dapper will call this method to get the list of bindings the app will get invoked by. In this example, we are telling Dapr // To invoke our app with a binding named storage func (s *server) GetBindingsSubscriptions(ctx context.Context, in *empty.Empty) (*pb.GetBindingsSubscriptionsEnvelope, error) { return &pb.GetBindingsSubscriptionsEnvelope{ Bindings: []string{"storage"}, }, nil } // This method gets invoked every time a new event is fired from a registerd binding. The message carries the binding name, a payload and optional metadata func (s *server) OnBindingEvent(ctx context.Context, in *pb.BindingEventEnvelope) (*pb.BindingResponseEnvelope, error) { fmt.Println("Invoked from binding") return &pb.BindingResponseEnvelope{}, nil } // This method is fired whenever a message has been published to a topic that has been subscribed. Dapr sends published messages in a CloudEvents 0.3 envelope. func (s *server) OnTopicEvent(ctx context.Context, in *pb.CloudEventEnvelope) (*empty.Empty, error) { fmt.Println("Topic message arrived") return &empty.Empty{}, nil }
-
使用 Dapr 命令啟動 StorageService
dapr run --app-id client --protocol grpc --app-port 4000 go run main.go
注意:
- Dapr 使用 App 端口號應與服務端口號相同,使用 --protocal grpc 指定通訊協議為 grpc 。此外,OnInvoke 中的 switch 方法用於調用者路由。
使用 ASP.NET Core
創建 StorageService
-
使用 NuGet 獲取程序管理包控制台安裝以下包
- Dapr.AspNetCore
- Dapr.Client.Grpc
- Grpc.AspNetCore
- Grpc.Net.Client
- Grpc.Tools
-
Startup.cs
文件中修改代碼如下:/// <summary> /// This method gets called by the runtime. Use this method to add services to the container. /// </summary> /// <param name="services">Services.</param> public void ConfigureServices(IServiceCollection services) { services.AddControllers().AddDapr(); services.AddDbContextPool<StorageContext>(options => { options.UseMySql(Configuration.GetConnectionString("MysqlConnection")); }); }
/// <summary> /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// </summary> /// <param name="app">app.</param> /// <param name="env">env.</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseCloudEvents(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapSubscribeHandler(); endpoints.MapControllers(); }); }
-
添加
StorageController.cs
文件,內容如下using System; using System.Linq; using System.Threading.Tasks; using Dapr.Client.Grpc; using Google.Protobuf; using Grpc.Net.Client; using Microsoft.AspNetCore.Mvc; using StorageService.Api.Entities; namespace StorageService.Api.Controllers { [ApiController] public class StorageController : ControllerBase { private readonly StorageContext _storageContext; public StorageController(StorageContext storageContext) { _storageContext = storageContext; } /// <summary> /// 初始化倉庫. /// </summary> /// <returns>是否成功.</returns> [HttpGet("InitialStorage")] public async Task<bool> InitialStorage() { string defaultPort = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT") ?? "54681"; // Set correct switch to make insecure gRPC service calls. This switch must be set before creating the GrpcChannel. AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); // Create Client string daprUri = $"http://127.0.0.1:{defaultPort}"; GrpcChannel channel = GrpcChannel.ForAddress(daprUri); var client = new Dapr.Client.Grpc.Dapr.DaprClient(channel); Console.WriteLine(daprUri); InvokeServiceResponseEnvelope result = await client.InvokeServiceAsync(new InvokeServiceEnvelope { Method = "MyMethod", Id = "client", Data = new Google.Protobuf.WellKnownTypes.Any { Value = ByteString.CopyFromUtf8("Hello ProductService") } }); Console.WriteLine("this is call result:" + result.Data.Value.ToStringUtf8()); //var productResult = result.Data.Unpack<ProductList.V1.ProductList>(); //Console.WriteLine("this is call result:" + productResult.Results.FirstOrDefault()); return true; } /// <summary> /// 修改庫存 /// </summary> /// <param name="storage"></param> /// <returns></returns> [HttpPut("Reduce")] public bool Reduce(Storage storage) { Storage storageFromDb = _storageContext.Storage.FirstOrDefault(q => q.ProductID.Equals(storage.ProductID)); if (storageFromDb == null) { return false; } if (storageFromDb.Amount <= storage.Amount) { return false; } storageFromDb.Amount -= storage.Amount; return true; } } }
-
使用 Dapr cli 啟用 StorageService 服務
dapr run --app-id storageService --app-port 5003 dotnet run
-
使用 curl 命令訪問 StorageService InitialStorage 方法
curl -X GET http://localhost:56349/v1.0/invoke/storageService/method/InitialStorage
輸入
true
其中打印信息為:
this is call result:Hi there!
注意:
- Dapr 使用 App 端口號應與服務端口號相同,例如:
ASP.Net Core
服務端口號為5003,則在使用 Dapr 托管應用程序時的端口號也應使用 5003,在 Client.InvokeServiceAsync 中的 Id 指被調用方的 App-Id ,Method 指被調用方方法名稱。參考 Go Server 中 OnInvoke 方法的 Switch 。