大纲
第一部分给出参考的资料,第二部分是代码使用的数据库,并对PostgreSQl进行的简单的介绍以及pgadmin使用时的注意事项。第三部分介绍了dapr如何处理数据库连接,第四部分讲解dapr程序如何调试,包括编译型语言和解释性语言。整个流程下来,后台系统的雏形就搭建起来了。
参考
-
https://docs.dapr.io/zh-hans/developing-applications/ides/vscode/
-
https://juejin.cn/post/7030235190521757727
-
https://www.postgresql.org/
-
https://docs.dapr.io/zh-hans/reference/components-reference/supported-bindings/mysql/
数据库PostgreSQl
Postgresql:The World’s Most Advanced Open Source Relational Database
这篇文章对PostgreSQl进行了比较详细的介绍,简而言之,全栈数据库。其超越Myql只是时间问题。
本文使用docker进行PostGresql的安装。
docker-compose.yml文件如下:
version: '3.4'
services:
pgadmin:
container_name: pgadmin4_container
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: admin@admin.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"
db:
image: samples/postgres
container_name: postgres
ports:
- "5432:5432"
build:
context: .
dockerfile: ./Dockerfile
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: docker
POSTGRES_DB: orders
这里又一点需要需要注意,使用pgadmin链接数据库的时候,
其中host name/address一栏中填写的地需要通过docker inspect postgres
来查看,选择这里面的IPAddress中的地址才可以连接,本地127.0.0.1/localhost都是无法连接上的。
连接之后可以查看表结构等信息。
dapr处理数据库连接
dapr使使用bindings building block来实现对数据库等外部资源的处理。
不需要复杂的orm框架处理,不需要繁琐的配置,只需要几行代码就能实现CRUD等基本操作,大大简化了代码的编写。
这里使用http方式来处理数据的插入和查询。简单说一下代码的含义,启动一个定时任务,这里通过cron来实现的,每隔10s钟就会进行一个插入以及查询操作。
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
//dapr run --app-id batch-http --app-port 7001 --resources-path ../../../components -- dotnet run
var cronBindingName = "cron";
var sqlBindingName = "sqldb";
var baseURL = Environment.GetEnvironmentVariable("BASE_URL") ?? "http://localhost";
var daprPort = Environment.GetEnvironmentVariable("DAPR_HTTP_PORT") ?? "3500";
var daprUrl = $"{baseURL}:{daprPort}/v1.0/bindings/{sqlBindingName}";
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
if (app.Environment.IsDevelopment()) {app.UseDeveloperExceptionPage();}
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
// Triggered by Dapr input binding
app.MapPost("/" + cronBindingName, async () => {
Console.WriteLine("Processing batch..");
string jsonFile = File.ReadAllText("../../../orders.json");
var ordersArray = JsonSerializer.Deserialize<Orders>(jsonFile);
foreach(Order ord in ordersArray?.orders ?? new Order[] {}){
var sqlText = $"insert into orders (orderid, customer, price) values ({ord.OrderId}, '{ord.Customer}', {ord.Price});";
var payload = new DaprPayload(sql: new DaprPostgresBindingMetadata(cmd: sqlText), operation: "exec");
var orderJson = JsonSerializer.Serialize<DaprPayload>(payload);
var content = new StringContent(orderJson, Encoding.UTF8, "application/json");
Console.WriteLine(sqlText);
// Insert order using Dapr output binding via HTTP Post
try {
var resp = await httpClient.PostAsync(daprUrl, content);
resp.EnsureSuccessStatusCode();
}
catch (HttpRequestException e) {
Console.WriteLine(e.ToString());
throw e;
}
var sqlQueryText = $"select * from orders;";
var payloadQuery = new DaprPayload(sql: new DaprPostgresBindingMetadata(cmd: sqlQueryText), operation: "query");
var orderQueryJson = JsonSerializer.Serialize<DaprPayload>(payloadQuery);
var queryContent = new StringContent(orderQueryJson, Encoding.UTF8, "application/json");
try {
var resp = await httpClient.PostAsync(daprUrl, queryContent);
Console.WriteLine(resp.GetType());
var result = await resp.Content.ReadAsStringAsync();
Console.WriteLine(result);
resp.EnsureSuccessStatusCode();
}
catch (HttpRequestException e) {
Console.WriteLine(e.ToString());
throw e;
}
}
Console.WriteLine("Finished processing batch");
return Results.Ok();
});
await app.RunAsync();
public record DaprPostgresBindingMetadata([property: JsonPropertyName("sql")] string cmd);
public record DaprPayload([property: JsonPropertyName("metadata")] DaprPostgresBindingMetadata sql, [property: JsonPropertyName("operation")] string operation);
public record Order([property: JsonPropertyName("orderid")] int OrderId, [property: JsonPropertyName("customer")] string Customer, [property: JsonPropertyName("price")] float Price);
public record Orders([property: JsonPropertyName("orders")] Order[] orders);
具体可见这里。
运行结果:
只需要稍微简单的封装就可以实现orm的功能,这里不再赘述。
接下来,来说本文的重点,如何调试dapr应用。
调试
光依赖Console.WriteLine来进行结果的查看是非常麻烦的,而且很多时候又去多内容是无法打印出来的,这个时候只能通过调试来解决问题。Dapr提供了一个Dapr Visual Studio Code扩展 ,用于Dapr应用程序的本地开发和调试。
dapr相比其他的微服务框架,比如springCloud的一大优势就是跨语言,具体可参见为什么Dapr是比SpringCloud和Istio更优雅的微服务框架?。通常来说python在机器学习领域是比较主流的语言,而Java在生态领域非常完善,可以应用在很多场景,.Net一门语言可以跨多个平台(包括前后端),node作为全栈开发语言的异步处理特性在视频聊天中可以用作信令服务器,每一种语言都有其优势和缺点,要结合具体场景使用,很多时候,我们需要使用多个语言来进行项目开发,这个时候,dapr的优势就非常明显了。
本文主要针对node,python和c#的调试,其他场景类似,使用的IDE是VSCode。
node和python
解释型语言的配置。
launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Nodeapp with Dapr",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/node/app.js",
"preLaunchTask": "daprd-debug-node",
"postDebugTask": "daprd-down-node"
},
{
"type": "python",
"request": "launch",
"name": "Pythonapp with Dapr",
"program": "${workspaceFolder}/python/app.py",
"console": "integratedTerminal",
"preLaunchTask": "daprd-debug-python",
"postDebugTask": "daprd-down-python"
}
],
"compounds": [
{
"name": "Node/Python Dapr",
"configurations": ["Nodeapp with Dapr","Pythonapp with Dapr"]
}
]
}
在 上面的例子里,将启动两个应用程序,每个应用程序都有自己的 Dapr sidecar。 一个是用Node.JS编写的,另一个是用Python编写的。 你会注意到每个配置都包含一个 daprd run preLaunchTask和一个 daprd stop postDebugTask。
每个配置都需要一个 request, type 和name。 这些参数可帮助 VSCode 识别 .vscode/task.json 文件中的任务配置。
- type 定义所使用的语言。 根据语言的不同,它可能需要在市场上找到一个扩展,如 Python扩展。
- name 是配置的唯一名称。 当在你的项目中调用多个配置时,这被用于复合配置。
- ${workspaceFolder} 是一个VS代码的变量引用。 这是在VS Code中打开的工作区的路径。
- preLaunchTask 和 postDebugTask 参数是指启动应用程序之前和之后运行的程序配置。 有关如何配置这些选项,请参阅步骤 2。
复合启动配置可以在 .vscode/launch.json 中定义,并且是并行启动的两个或多个启动配置的集合。 (可选)可以在启动各个调试会话之前指定并运行 预启动任务 。如上述文件中的compounds配置。
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"appId": "nodeapp",
"appPort": 3000,
"httpPort": 3500,
"metricsPort": 9090,
"label": "daprd-debug-node",
"type": "daprd"
},
{
"appId": "nodeapp",
"label": "daprd-down-node",
"type": "daprd-down"
},
{
"appId": "pythonapp",
"httpPort": 53109,
"grpcPort": 53317,
"metricsPort": 9091,
"label": "daprd-debug-python",
"type": "daprd"
},
{
"appId": "pythonapp",
"label": "daprd-down-python",
"type": "daprd-down"
}
]
}
对于每个 在 .vscode/launch.json 中定义的任务 ,必须在 .vscode/task.json中存在一个相应的任务定义。
每个服务都需要一个任务来启动具有 daprd 类型的 Dapr SideCar,以及一个使用 daprd-down停止SideCar的任务。 参数 appId, httpPort, metricsPort, label 和 type 是必须的。 还有其他的可选参数,
按照上面的配置完成之后,就可以进行调试了,如下图所示。
C#程序
编译型语言的配置。*
lauch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (Dapr)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "dapr-debug-clr",
"postDebugTask": "daprd-down-clr",
"program": "${workspaceFolder}/csharp/http/batch/bin/Debug/net6.0/batch.dll",
"args": [],
"cwd": "${workspaceFolder}/csharp/http/batch",
"stopAtEntry": false,
"console": "internalConsole",
// "serverReadyAction": {
// "action": "openExternally",
// "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"
// },
// "envFile": "${workspaceFolder}/dapr.env"
}
]
}
tasks.json
{
"version": "2.0.0",
"tasks": [
// {
// "label": "build",
// "command": "dotnet",
// "type": "process",
// "args": [
// "build",
// "${workspaceFolder}/csharp/http/batch/batch.csproj"
// ],
// "problemMatcher": "$msCompile"
// },
{
"label": "dapr-debug-clr",
"command": "daprd",
"args": [
"--app-id", "batch-http",
"--app-port", "7001",
"--components-path", "${workspaceFolder}/components",
"--log-level", "info",
"dotnet",
"run"
],
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/csharp/http/batch"
},
"dependsOn": [
// "build"
]
},
{
"appId": "batch-http",
"label": "daprd-down-clr",
"type": "daprd-down"
}
]
}
command 使用的是daprd。
下图是调试上文中c#代码的结果,可见端点有效。
注意:在启动过程中会出现弹出框,“The specified task cannot be tracked” ,点击Debug anyway就行,在这里有具体的处理方案。
写在最后
五一假期基本结束了。加油。
公众号
更多内容,欢迎关注我的微信公众号:半夏之夜的无情剑客。