14 changed files with 2531 additions and 109 deletions
@ -0,0 +1,181 @@ |
|||||
|
# LINGYUN.Abp.Dapr.Actors.AspNetCore.Wrapper |
||||
|
|
||||
|
Dapr Actors ASP.NET Core wrapper module for handling Actor method call response wrapping and unwrapping. |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* Automatic Actor response result wrapping/unwrapping |
||||
|
* Integration with ABP's wrapper system |
||||
|
* Error handling for Actor method calls |
||||
|
* Support for success/error code configuration |
||||
|
* Integration with Dapr.Actors.AspNetCore |
||||
|
* Support for custom response wrapper format |
||||
|
* Flexible wrapping control options |
||||
|
|
||||
|
## Basic Usage |
||||
|
|
||||
|
Module reference as needed: |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn( |
||||
|
typeof(AbpDaprActorsAspNetCoreModule), |
||||
|
typeof(AbpWrapperModule) |
||||
|
)] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Configuration Options |
||||
|
|
||||
|
The module uses `AbpWrapperOptions` from the `LINGYUN.Abp.Wrapper` package for configuration: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"Wrapper": { |
||||
|
"IsEnabled": true, // Enable/disable response wrapping |
||||
|
"CodeWithSuccess": "0", // Success code in wrapped response |
||||
|
"HttpStatusCode": 200, // Default HTTP status code for wrapped responses |
||||
|
"WrapOnError": true, // Whether to wrap error responses |
||||
|
"WrapOnSuccess": true // Whether to wrap success responses |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Implementation Example |
||||
|
|
||||
|
1. Actor Interface Definition |
||||
|
|
||||
|
```csharp |
||||
|
public interface ICounterActor : IActor |
||||
|
{ |
||||
|
Task<int> GetCountAsync(); |
||||
|
Task IncrementCountAsync(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. Actor Implementation |
||||
|
|
||||
|
```csharp |
||||
|
public class CounterActor : Actor, ICounterActor |
||||
|
{ |
||||
|
private const string CountStateName = "count"; |
||||
|
|
||||
|
public CounterActor(ActorHost host) : base(host) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<int> GetCountAsync() |
||||
|
{ |
||||
|
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
||||
|
return count.HasValue ? count.Value : 0; |
||||
|
} |
||||
|
|
||||
|
public async Task IncrementCountAsync() |
||||
|
{ |
||||
|
var currentCount = await GetCountAsync(); |
||||
|
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Response Format |
||||
|
|
||||
|
When wrapping is enabled, Actor method responses will be in the following format: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"code": "0", // Response code, "0" indicates success by default |
||||
|
"message": "Success", // Response message |
||||
|
"details": null, // Additional details (optional) |
||||
|
"result": { // Actual response data |
||||
|
// ... Actor method return value |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Error Handling |
||||
|
|
||||
|
The module automatically handles Actor method call errors: |
||||
|
* For wrapped responses: |
||||
|
* Unwraps the response and checks the code |
||||
|
* If code doesn't match `CodeWithSuccess`, throws `AbpRemoteCallException` |
||||
|
* Includes error message, details, and code in the exception |
||||
|
* Supports custom error code mapping |
||||
|
* For Actor runtime errors: |
||||
|
* Automatically wraps as standard error response |
||||
|
* Preserves original exception information |
||||
|
* Includes Actor-related context information |
||||
|
|
||||
|
### Error Response Example |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"code": "ERROR_001", |
||||
|
"message": "Actor method call failed", |
||||
|
"details": "Failed to access state for actor 'counter'", |
||||
|
"result": null |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Advanced Usage |
||||
|
|
||||
|
### 1. Controlling Response Wrapping |
||||
|
|
||||
|
Response wrapping can be controlled per Actor call using HTTP headers: |
||||
|
|
||||
|
```csharp |
||||
|
// Add to request headers |
||||
|
var headers = new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "X-Abp-Wrap-Result", "true" }, // Force enable wrapping |
||||
|
// or |
||||
|
{ "X-Abp-Dont-Wrap-Result", "true" } // Force disable wrapping |
||||
|
}; |
||||
|
|
||||
|
// Use in Actor method |
||||
|
public async Task<int> GetCountAsync() |
||||
|
{ |
||||
|
var context = ActorContext.GetContext(); |
||||
|
context.Headers.Add("X-Abp-Wrap-Result", "true"); |
||||
|
|
||||
|
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
||||
|
return count.HasValue ? count.Value : 0; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. Custom Error Handling |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomActorErrorHandler : IAbpWrapperErrorHandler |
||||
|
{ |
||||
|
public Task HandleAsync(AbpWrapperErrorContext context) |
||||
|
{ |
||||
|
if (context.Exception is ActorMethodInvocationException actorException) |
||||
|
{ |
||||
|
// Custom Actor error handling logic |
||||
|
context.Response = new WrapperResponse |
||||
|
{ |
||||
|
Code = "ACTOR_ERROR", |
||||
|
Message = actorException.Message, |
||||
|
Details = actorException.ActorId |
||||
|
}; |
||||
|
} |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Important Notes |
||||
|
|
||||
|
* Response wrapping can be controlled through: |
||||
|
* Global settings in configuration |
||||
|
* HTTP headers for individual requests |
||||
|
* Dynamic control in Actor methods |
||||
|
* Error responses maintain original error structure for Actor methods |
||||
|
* The module integrates with ABP's remote service error handling system |
||||
|
* Recommended to use response wrapping consistently in microservices architecture |
||||
|
* Wrapper format can be customized by implementing `IAbpWrapperResponseBuilder` |
||||
|
* Actor state operation errors are properly wrapped and handled |
||||
|
|
||||
|
[查看中文](README.md) |
||||
@ -0,0 +1,181 @@ |
|||||
|
# LINGYUN.Abp.Dapr.Actors.AspNetCore.Wrapper |
||||
|
|
||||
|
Dapr Actors ASP.NET Core响应包装模块,用于处理Actor方法调用的响应包装和解包。 |
||||
|
|
||||
|
## 功能特性 |
||||
|
|
||||
|
* Actor响应结果自动包装/解包 |
||||
|
* 与ABP包装系统集成 |
||||
|
* 支持Actor方法调用的错误处理 |
||||
|
* 支持成功/错误代码配置 |
||||
|
* 与Dapr.Actors.AspNetCore集成 |
||||
|
* 支持自定义响应包装格式 |
||||
|
* 灵活的包装控制选项 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
模块按需引用: |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn( |
||||
|
typeof(AbpDaprActorsAspNetCoreModule), |
||||
|
typeof(AbpWrapperModule) |
||||
|
)] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 配置选项 |
||||
|
|
||||
|
模块使用来自`LINGYUN.Abp.Wrapper`包的`AbpWrapperOptions`进行配置: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"Wrapper": { |
||||
|
"IsEnabled": true, // 启用/禁用响应包装 |
||||
|
"CodeWithSuccess": "0", // 包装响应中的成功代码 |
||||
|
"HttpStatusCode": 200, // 包装响应的默认HTTP状态码 |
||||
|
"WrapOnError": true, // 是否包装错误响应 |
||||
|
"WrapOnSuccess": true // 是否包装成功响应 |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 实现示例 |
||||
|
|
||||
|
1. Actor接口定义 |
||||
|
|
||||
|
```csharp |
||||
|
public interface ICounterActor : IActor |
||||
|
{ |
||||
|
Task<int> GetCountAsync(); |
||||
|
Task IncrementCountAsync(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. Actor实现 |
||||
|
|
||||
|
```csharp |
||||
|
public class CounterActor : Actor, ICounterActor |
||||
|
{ |
||||
|
private const string CountStateName = "count"; |
||||
|
|
||||
|
public CounterActor(ActorHost host) : base(host) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<int> GetCountAsync() |
||||
|
{ |
||||
|
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
||||
|
return count.HasValue ? count.Value : 0; |
||||
|
} |
||||
|
|
||||
|
public async Task IncrementCountAsync() |
||||
|
{ |
||||
|
var currentCount = await GetCountAsync(); |
||||
|
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 响应格式 |
||||
|
|
||||
|
当启用包装时,Actor方法的响应将采用以下格式: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"code": "0", // 响应代码,默认"0"表示成功 |
||||
|
"message": "Success", // 响应消息 |
||||
|
"details": null, // 附加详情(可选) |
||||
|
"result": { // 实际响应数据 |
||||
|
// ... Actor方法的返回值 |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 错误处理 |
||||
|
|
||||
|
模块自动处理Actor方法调用的错误: |
||||
|
* 对于包装的响应: |
||||
|
* 解包响应并检查代码 |
||||
|
* 如果代码与`CodeWithSuccess`不匹配,抛出`AbpRemoteCallException` |
||||
|
* 在异常中包含错误消息、详情和代码 |
||||
|
* 支持自定义错误代码映射 |
||||
|
* 对于Actor运行时错误: |
||||
|
* 自动包装为标准错误响应 |
||||
|
* 保留原始异常信息 |
||||
|
* 包含Actor相关的上下文信息 |
||||
|
|
||||
|
### 错误响应示例 |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"code": "ERROR_001", |
||||
|
"message": "Actor方法调用失败", |
||||
|
"details": "Actor 'counter' 的状态访问失败", |
||||
|
"result": null |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 高级用法 |
||||
|
|
||||
|
### 1. 控制响应包装 |
||||
|
|
||||
|
可以通过HTTP头控制单个Actor调用的响应包装: |
||||
|
|
||||
|
```csharp |
||||
|
// 在请求头中添加 |
||||
|
var headers = new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "X-Abp-Wrap-Result", "true" }, // 强制启用包装 |
||||
|
// 或 |
||||
|
{ "X-Abp-Dont-Wrap-Result", "true" } // 强制禁用包装 |
||||
|
}; |
||||
|
|
||||
|
// 在Actor方法中使用 |
||||
|
public async Task<int> GetCountAsync() |
||||
|
{ |
||||
|
var context = ActorContext.GetContext(); |
||||
|
context.Headers.Add("X-Abp-Wrap-Result", "true"); |
||||
|
|
||||
|
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
||||
|
return count.HasValue ? count.Value : 0; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. 自定义错误处理 |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomActorErrorHandler : IAbpWrapperErrorHandler |
||||
|
{ |
||||
|
public Task HandleAsync(AbpWrapperErrorContext context) |
||||
|
{ |
||||
|
if (context.Exception is ActorMethodInvocationException actorException) |
||||
|
{ |
||||
|
// 自定义Actor错误处理逻辑 |
||||
|
context.Response = new WrapperResponse |
||||
|
{ |
||||
|
Code = "ACTOR_ERROR", |
||||
|
Message = actorException.Message, |
||||
|
Details = actorException.ActorId |
||||
|
}; |
||||
|
} |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 注意事项 |
||||
|
|
||||
|
* 响应包装可以通过以下方式控制: |
||||
|
* 配置文件中的全局设置 |
||||
|
* HTTP头控制单个请求 |
||||
|
* Actor方法中的动态控制 |
||||
|
* Actor方法的错误响应会保持原始错误结构 |
||||
|
* 模块与ABP的远程服务错误处理系统集成 |
||||
|
* 建议在微服务架构中统一使用响应包装 |
||||
|
* 包装格式可以通过继承`IAbpWrapperResponseBuilder`自定义 |
||||
|
* Actor状态操作的错误会被正确包装和处理 |
||||
|
|
||||
|
[查看英文](README.EN.md) |
||||
@ -0,0 +1,123 @@ |
|||||
|
# LINGYUN.Abp.Dapr.Actors.AspNetCore |
||||
|
|
||||
|
Integration of Dapr.Actors with ASP.NET Core in the ABP framework. This module automatically scans and registers Actor services defined within assemblies as Dapr.Actors. |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* Automatic Actor service registration |
||||
|
* Integration with ABP's dependency injection system |
||||
|
* Support for custom Actor type names through `RemoteServiceAttribute` |
||||
|
* Actor runtime configuration through `ActorRuntimeOptions` |
||||
|
* Automatic Actor endpoint mapping |
||||
|
* Actor interface validation |
||||
|
|
||||
|
## Basic Usage |
||||
|
|
||||
|
Module reference as needed: |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpDaprActorsAspNetCoreModule))] |
||||
|
public class YourProjectModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Configure Actor runtime options |
||||
|
PreConfigure<ActorRuntimeOptions>(options => |
||||
|
{ |
||||
|
options.ActorIdleTimeout = TimeSpan.FromMinutes(60); |
||||
|
options.ActorScanInterval = TimeSpan.FromSeconds(30); |
||||
|
options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(30); |
||||
|
options.DrainRebalancedActors = true; |
||||
|
options.RemindersStoragePartitions = 7; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Implementation Example |
||||
|
|
||||
|
1. Define Actor Interface |
||||
|
|
||||
|
```csharp |
||||
|
[RemoteService("counter")] // Optional: customize Actor type name |
||||
|
public interface ICounterActor : IActor |
||||
|
{ |
||||
|
Task<int> GetCountAsync(); |
||||
|
Task IncrementCountAsync(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. Implement Actor |
||||
|
|
||||
|
```csharp |
||||
|
public class CounterActor : Actor, ICounterActor |
||||
|
{ |
||||
|
private const string CountStateName = "count"; |
||||
|
|
||||
|
public CounterActor(ActorHost host) : base(host) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<int> GetCountAsync() |
||||
|
{ |
||||
|
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
||||
|
return count.HasValue ? count.Value : 0; |
||||
|
} |
||||
|
|
||||
|
public async Task IncrementCountAsync() |
||||
|
{ |
||||
|
var currentCount = await GetCountAsync(); |
||||
|
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The module will automatically: |
||||
|
1. Detect the `CounterActor` implementation |
||||
|
2. Register it with Dapr.Actors |
||||
|
3. Configure the Actor runtime |
||||
|
4. Map the Actor endpoints |
||||
|
|
||||
|
## Actor Runtime Configuration |
||||
|
|
||||
|
The module supports all standard Dapr Actor runtime configurations through `ActorRuntimeOptions`: |
||||
|
|
||||
|
```csharp |
||||
|
PreConfigure<ActorRuntimeOptions>(options => |
||||
|
{ |
||||
|
// Actor timeout settings |
||||
|
options.ActorIdleTimeout = TimeSpan.FromMinutes(60); |
||||
|
options.ActorScanInterval = TimeSpan.FromSeconds(30); |
||||
|
|
||||
|
// Draining settings |
||||
|
options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(30); |
||||
|
options.DrainRebalancedActors = true; |
||||
|
|
||||
|
// Reminders settings |
||||
|
options.RemindersStoragePartitions = 7; |
||||
|
|
||||
|
// Custom serialization settings |
||||
|
options.JsonSerializerOptions = new JsonSerializerOptions |
||||
|
{ |
||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase |
||||
|
}; |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## Important Notes |
||||
|
|
||||
|
* Actor implementations must be registered in the dependency injection container |
||||
|
* Actor interfaces must inherit from `IActor` |
||||
|
* Actor type names can be customized using the `RemoteServiceAttribute` |
||||
|
* The module automatically maps Actor endpoints using ABP's endpoint routing system |
||||
|
* Actor runtime options should be configured in the `PreConfigureServices` phase |
||||
|
|
||||
|
## Endpoint Mapping |
||||
|
|
||||
|
The module automatically maps the following Actor endpoints: |
||||
|
* `/dapr/actors/{actorType}/{actorId}/method/{methodName}` |
||||
|
* `/dapr/actors/{actorType}/{actorId}/state` |
||||
|
* `/dapr/actors/{actorType}/{actorId}/reminders/{reminderName}` |
||||
|
* `/dapr/actors/{actorType}/{actorId}/timers/{timerName}` |
||||
|
|
||||
|
[查看中文](README.md) |
||||
@ -0,0 +1,129 @@ |
|||||
|
# LINGYUN.Abp.Dapr.Actors |
||||
|
|
||||
|
Dapr.IActor client proxy module |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* Dynamic proxy generation for Dapr Actors |
||||
|
* Integration with ABP's remote service system |
||||
|
* Support for Actor authentication and authorization |
||||
|
* Multi-tenant support |
||||
|
* Automatic request/response handling |
||||
|
* Custom error handling |
||||
|
* Culture and language header support |
||||
|
|
||||
|
## Basic Usage |
||||
|
|
||||
|
Module reference as needed: |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpDaprActorsModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Register proxies similar to Volo.Abp.Http.Client module |
||||
|
context.Services.AddDaprActorProxies( |
||||
|
typeof(YouProjectActorInterfaceModule).Assembly, // Search for IActor definitions in YouProjectActorInterfaceModule |
||||
|
RemoteServiceName |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Configuration Options |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"RemoteServices": { |
||||
|
"Default": { |
||||
|
"BaseUrl": "http://localhost:3500", // Required, Dapr HTTP endpoint |
||||
|
"DaprApiToken": "your-api-token", // Optional, Dapr API Token |
||||
|
"RequestTimeout": "30000" // Optional, request timeout in milliseconds (default: 30000) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Implementation Example |
||||
|
|
||||
|
1. Actor Interface Definition |
||||
|
|
||||
|
```csharp |
||||
|
public interface ICounterActor : IActor |
||||
|
{ |
||||
|
Task<int> GetCountAsync(); |
||||
|
Task IncrementCountAsync(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. Actor Implementation |
||||
|
|
||||
|
```csharp |
||||
|
public class CounterActor : Actor, ICounterActor |
||||
|
{ |
||||
|
private const string CountStateName = "count"; |
||||
|
|
||||
|
public CounterActor(ActorHost host) : base(host) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<int> GetCountAsync() |
||||
|
{ |
||||
|
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
||||
|
return count.HasValue ? count.Value : 0; |
||||
|
} |
||||
|
|
||||
|
public async Task IncrementCountAsync() |
||||
|
{ |
||||
|
var currentCount = await GetCountAsync(); |
||||
|
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
3. Client Usage |
||||
|
|
||||
|
```csharp |
||||
|
public class CounterService |
||||
|
{ |
||||
|
private readonly ICounterActor _counterActor; |
||||
|
|
||||
|
public CounterService(ICounterActor counterActor) |
||||
|
{ |
||||
|
_counterActor = counterActor; |
||||
|
} |
||||
|
|
||||
|
public async Task<int> GetAndIncrementCountAsync() |
||||
|
{ |
||||
|
var count = await _counterActor.GetCountAsync(); |
||||
|
await _counterActor.IncrementCountAsync(); |
||||
|
return count; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Important Notes |
||||
|
|
||||
|
* Actor methods must return `Task` or `Task<T>` |
||||
|
* Actor methods can have at most one parameter |
||||
|
* Actor instances are single-threaded, processing one request at a time |
||||
|
* Actor state is managed by the Dapr runtime |
||||
|
* The module automatically handles: |
||||
|
* Authentication headers |
||||
|
* Tenant context |
||||
|
* Culture information |
||||
|
* Request timeouts |
||||
|
* Error handling |
||||
|
|
||||
|
## Error Handling |
||||
|
|
||||
|
The module provides custom error handling for Actor calls: |
||||
|
* `AbpDaprActorCallException`: Thrown when an Actor method call fails |
||||
|
* `ActorMethodInvocationException`: Contains detailed information about the failure |
||||
|
* Error responses include: |
||||
|
* Error message |
||||
|
* Error code |
||||
|
* Original exception type |
||||
|
|
||||
|
[查看中文](README.md) |
||||
@ -0,0 +1,189 @@ |
|||||
|
# LINGYUN.Abp.Dapr.Client.Wrapper |
||||
|
|
||||
|
Dapr service-to-service invocation module that handles wrapped response result unpacking. |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* Automatic response result wrapping/unwrapping |
||||
|
* Integration with ABP's wrapper system |
||||
|
* Custom error handling for wrapped responses |
||||
|
* Support for success/error code configuration |
||||
|
* HTTP status code mapping |
||||
|
* Support for custom response wrapper format |
||||
|
* Flexible wrapping control options |
||||
|
|
||||
|
## Basic Usage |
||||
|
|
||||
|
Module reference as needed: |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpDaprClientModule))] |
||||
|
public class AbpDaprClientWrapperModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Configuration Options |
||||
|
|
||||
|
The module uses `AbpWrapperOptions` from the `LINGYUN.Abp.Wrapper` package for configuration: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"Wrapper": { |
||||
|
"IsEnabled": true, // Enable/disable response wrapping |
||||
|
"CodeWithSuccess": "0", // Success code in wrapped response |
||||
|
"HttpStatusCode": 200, // Default HTTP status code for wrapped responses |
||||
|
"WrapOnError": true, // Whether to wrap error responses |
||||
|
"WrapOnSuccess": true // Whether to wrap success responses |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Implementation Example |
||||
|
|
||||
|
1. Service Definition |
||||
|
|
||||
|
```csharp |
||||
|
public interface IProductService |
||||
|
{ |
||||
|
Task<ProductDto> GetAsync(string id); |
||||
|
Task<List<ProductDto>> GetListAsync(); |
||||
|
Task<ProductDto> CreateAsync(CreateProductDto input); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. Service Implementation |
||||
|
|
||||
|
```csharp |
||||
|
public class ProductService : IProductService |
||||
|
{ |
||||
|
private readonly DaprClient _daprClient; |
||||
|
|
||||
|
public ProductService(DaprClient daprClient) |
||||
|
{ |
||||
|
_daprClient = daprClient; |
||||
|
} |
||||
|
|
||||
|
public async Task<ProductDto> GetAsync(string id) |
||||
|
{ |
||||
|
// Response wrapping is handled automatically |
||||
|
return await _daprClient.InvokeMethodAsync<ProductDto>( |
||||
|
"product-service", // Target service ID |
||||
|
$"api/products/{id}", // Method path |
||||
|
HttpMethod.Get |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<ProductDto>> GetListAsync() |
||||
|
{ |
||||
|
return await _daprClient.InvokeMethodAsync<List<ProductDto>>( |
||||
|
"product-service", |
||||
|
"api/products", |
||||
|
HttpMethod.Get |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public async Task<ProductDto> CreateAsync(CreateProductDto input) |
||||
|
{ |
||||
|
return await _daprClient.InvokeMethodAsync<ProductDto>( |
||||
|
"product-service", |
||||
|
"api/products", |
||||
|
HttpMethod.Post, |
||||
|
input |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Response Format |
||||
|
|
||||
|
When wrapping is enabled, the response will be in the following format: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"code": "0", // Response code, "0" indicates success by default |
||||
|
"message": "Success", // Response message |
||||
|
"details": null, // Additional details (optional) |
||||
|
"result": { // Actual response data |
||||
|
// ... response content |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Error Handling |
||||
|
|
||||
|
The module automatically handles error responses: |
||||
|
* For wrapped responses (with `AbpWrapResult` header): |
||||
|
* Unwraps the response and checks the code |
||||
|
* If code doesn't match `CodeWithSuccess`, throws `AbpRemoteCallException` |
||||
|
* Includes error message, details, and code in the exception |
||||
|
* Supports custom error code mapping |
||||
|
* For unwrapped responses: |
||||
|
* Passes through the original response |
||||
|
* Uses standard HTTP error handling |
||||
|
* Maintains original error information |
||||
|
|
||||
|
### Error Response Example |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"code": "ERROR_001", |
||||
|
"message": "Product not found", |
||||
|
"details": "Product with ID '123' does not exist", |
||||
|
"result": null |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Advanced Usage |
||||
|
|
||||
|
### 1. Controlling Response Wrapping |
||||
|
|
||||
|
Response wrapping can be controlled per request using HTTP headers: |
||||
|
|
||||
|
```csharp |
||||
|
// Add to request headers |
||||
|
var headers = new Dictionary<string, string> |
||||
|
{ |
||||
|
{ "X-Abp-Wrap-Result", "true" }, // Force enable wrapping |
||||
|
// or |
||||
|
{ "X-Abp-Dont-Wrap-Result", "true" } // Force disable wrapping |
||||
|
}; |
||||
|
|
||||
|
await _daprClient.InvokeMethodAsync<ProductDto>( |
||||
|
"product-service", |
||||
|
"api/products", |
||||
|
HttpMethod.Get, |
||||
|
null, |
||||
|
headers |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
### 2. Custom Error Handling |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomErrorHandler : IAbpWrapperErrorHandler |
||||
|
{ |
||||
|
public Task HandleAsync(AbpWrapperErrorContext context) |
||||
|
{ |
||||
|
// Custom error handling logic |
||||
|
if (context.Response.Code == "CUSTOM_ERROR") |
||||
|
{ |
||||
|
// Special handling |
||||
|
} |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Important Notes |
||||
|
|
||||
|
* Response wrapping can be controlled through: |
||||
|
* Global settings in configuration |
||||
|
* HTTP headers for individual requests |
||||
|
* Dynamic control in code |
||||
|
* Error responses maintain original error structure when possible |
||||
|
* The module integrates with ABP's remote service error handling system |
||||
|
* Recommended to use response wrapping consistently in microservices architecture |
||||
|
* Wrapper format can be customized by implementing `IAbpWrapperResponseBuilder` |
||||
|
|
||||
|
[查看中文](README.md) |
||||
@ -0,0 +1,277 @@ |
|||||
|
[Actors](../README.md) | Dapr.Client Documentation |
||||
|
|
||||
|
# LINGYUN.Abp.Dapr.Client |
||||
|
|
||||
|
Implements service-to-service invocation as described in the Dapr documentation. The project design is consistent with Volo.Abp.Http.Client and can seamlessly replace Volo.Abp.Http.Client through configuration. |
||||
|
|
||||
|
For configuration reference, see [AbpRemoteServiceOptions](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients#abpremoteserviceoptions) |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* Integration with ABP remote service system |
||||
|
* Dynamic proxy generation |
||||
|
* Service discovery and load balancing |
||||
|
* Custom request and response handling |
||||
|
* Error handling and formatting |
||||
|
* Multiple service endpoint configuration |
||||
|
* Request/response interceptors |
||||
|
* Custom DaprClient behavior support |
||||
|
|
||||
|
## Configuration Options |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"RemoteServices": { |
||||
|
"Default": { |
||||
|
"AppId": "default-app", // Dapr application ID |
||||
|
"BaseUrl": "http://localhost:3500", // Dapr HTTP endpoint |
||||
|
"HealthCheckUrl": "/health", // Health check endpoint |
||||
|
"RequestTimeout": 30000, // Request timeout in milliseconds |
||||
|
"RetryCount": 3, // Number of retry attempts |
||||
|
"RetryWaitTime": 1000 // Retry wait time in milliseconds |
||||
|
}, |
||||
|
"System": { |
||||
|
"AppId": "system-app", |
||||
|
"BaseUrl": "http://localhost:50000", |
||||
|
"Headers": { // Custom request headers |
||||
|
"Tenant": "Default", |
||||
|
"Culture": "en-US" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Basic Usage |
||||
|
|
||||
|
Module reference as needed: |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpDaprClientModule))] |
||||
|
public class YourProjectModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Register proxies similar to Volo.Abp.Http.Client module |
||||
|
context.Services.AddDaprClientProxies( |
||||
|
typeof(YourProjectInterfaceModule).Assembly, // Search for remote service definitions |
||||
|
RemoteServiceName |
||||
|
); |
||||
|
|
||||
|
// Configure proxy options |
||||
|
Configure<AbpDaprClientProxyOptions>(options => |
||||
|
{ |
||||
|
// Configure request interceptor |
||||
|
options.ProxyRequestActions.Add((appId, request) => |
||||
|
{ |
||||
|
request.Headers.Add("Custom-Header", "Value"); |
||||
|
}); |
||||
|
|
||||
|
// Configure response handling |
||||
|
options.OnResponse(async (response, serviceProvider) => |
||||
|
{ |
||||
|
return await response.Content.ReadAsStringAsync(); |
||||
|
}); |
||||
|
|
||||
|
// Configure error handling |
||||
|
options.OnError(async (response, serviceProvider) => |
||||
|
{ |
||||
|
var error = await response.Content.ReadAsStringAsync(); |
||||
|
return new RemoteServiceErrorInfo |
||||
|
{ |
||||
|
Code = response.StatusCode.ToString(), |
||||
|
Message = error |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Implementation Example |
||||
|
|
||||
|
### 1. Interface Definition |
||||
|
|
||||
|
```csharp |
||||
|
// IApplicationService implements IRemoteService |
||||
|
public interface ISystemAppService : IApplicationService |
||||
|
{ |
||||
|
Task<string> GetAsync(); |
||||
|
Task<SystemDto> CreateAsync(CreateSystemDto input); |
||||
|
Task<List<SystemDto>> GetListAsync(); |
||||
|
Task DeleteAsync(string id); |
||||
|
} |
||||
|
|
||||
|
public class SystemInterfaceModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. Server Implementation |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn( |
||||
|
typeof(SystemInterfaceModule), |
||||
|
typeof(AbpAspNetCoreMvcModule) |
||||
|
)] |
||||
|
public class SystemServerModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IMvcBuilder>(mvcBuilder => |
||||
|
{ |
||||
|
mvcBuilder.AddApplicationPartIfNotExists(typeof(SystemServerModule).Assembly); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class SystemAppService : ApplicationService, ISystemAppService |
||||
|
{ |
||||
|
private readonly ISystemRepository _systemRepository; |
||||
|
|
||||
|
public SystemAppService(ISystemRepository systemRepository) |
||||
|
{ |
||||
|
_systemRepository = systemRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task<string> GetAsync() |
||||
|
{ |
||||
|
return "System"; |
||||
|
} |
||||
|
|
||||
|
public async Task<SystemDto> CreateAsync(CreateSystemDto input) |
||||
|
{ |
||||
|
var system = await _systemRepository.CreateAsync( |
||||
|
new System |
||||
|
{ |
||||
|
Name = input.Name, |
||||
|
Description = input.Description |
||||
|
} |
||||
|
); |
||||
|
return ObjectMapper.Map<System, SystemDto>(system); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<SystemDto>> GetListAsync() |
||||
|
{ |
||||
|
var systems = await _systemRepository.GetListAsync(); |
||||
|
return ObjectMapper.Map<List<System>, List<SystemDto>>(systems); |
||||
|
} |
||||
|
|
||||
|
public async Task DeleteAsync(string id) |
||||
|
{ |
||||
|
await _systemRepository.DeleteAsync(id); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. Client Usage |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpDaprClientModule))] |
||||
|
public class SystemClientModule : AbpModule |
||||
|
{ |
||||
|
private const string RemoteServiceName = "System"; |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Register proxies |
||||
|
context.Services.AddDaprClientProxies( |
||||
|
typeof(SystemInterfaceModule).Assembly, |
||||
|
RemoteServiceName |
||||
|
); |
||||
|
|
||||
|
// Configure retry policy |
||||
|
context.Services.AddDaprClientBuilder(builder => |
||||
|
{ |
||||
|
builder.ConfigureHttpClient((sp, client) => |
||||
|
{ |
||||
|
client.Timeout = TimeSpan.FromSeconds(30); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class SystemService |
||||
|
{ |
||||
|
private readonly ISystemAppService _systemAppService; |
||||
|
|
||||
|
public SystemService(ISystemAppService systemAppService) |
||||
|
{ |
||||
|
_systemAppService = systemAppService; |
||||
|
} |
||||
|
|
||||
|
public async Task<List<SystemDto>> GetSystemsAsync() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return await _systemAppService.GetListAsync(); |
||||
|
} |
||||
|
catch (AbpRemoteCallException ex) |
||||
|
{ |
||||
|
// Handle remote call exception |
||||
|
_logger.LogError(ex, "Failed to get systems"); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Advanced Usage |
||||
|
|
||||
|
### 1. Custom Request Handling |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomRequestHandler |
||||
|
{ |
||||
|
public void Configure(HttpRequestMessage request) |
||||
|
{ |
||||
|
request.Headers.Add("Correlation-Id", Guid.NewGuid().ToString()); |
||||
|
request.Headers.Add("Client-Version", "1.0.0"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Register in module |
||||
|
Configure<AbpDaprClientProxyOptions>(options => |
||||
|
{ |
||||
|
options.ProxyRequestActions.Add((appId, request) => |
||||
|
{ |
||||
|
new CustomRequestHandler().Configure(request); |
||||
|
}); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
### 2. Custom Response Handling |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomResponseHandler |
||||
|
{ |
||||
|
public async Task<string> HandleAsync(HttpResponseMessage response) |
||||
|
{ |
||||
|
var content = await response.Content.ReadAsStringAsync(); |
||||
|
// Custom response handling logic |
||||
|
return content; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Register in module |
||||
|
Configure<AbpDaprClientProxyOptions>(options => |
||||
|
{ |
||||
|
options.OnResponse(async (response, sp) => |
||||
|
{ |
||||
|
return await new CustomResponseHandler().HandleAsync(response); |
||||
|
}); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## Important Notes |
||||
|
|
||||
|
* Remote service interfaces must inherit `IRemoteService` |
||||
|
* Configuration changes require recreating proxy instances |
||||
|
* Configure appropriate timeout and retry policies |
||||
|
* Error handling should consider network exceptions and service unavailability |
||||
|
* Enable service discovery in production environments |
||||
|
* Use health checks to ensure service availability |
||||
|
* Request header configuration should consider security and authentication requirements |
||||
|
* Logging is important for problem diagnosis |
||||
|
|
||||
|
[查看中文](README.md) |
||||
@ -0,0 +1,204 @@ |
|||||
|
# LINGYUN.Abp.Dapr |
||||
|
|
||||
|
Dapr integration base module, implementing the named singleton DaprClient as described in the Dapr documentation. |
||||
|
|
||||
|
See: https://docs.dapr.io/developing-applications/sdks/dotnet/dotnet-client/dotnet-daprclient-usage |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* Support for creating default and named DaprClient instances |
||||
|
* Support for configuring HTTP and gRPC endpoints |
||||
|
* Support for custom JSON serialization options |
||||
|
* Support for Dapr API Token authentication |
||||
|
* Support for gRPC channel configuration |
||||
|
* Support for DaprClient instance configuration and builder configuration extensions |
||||
|
* Support for multiple Dapr Sidecar connections |
||||
|
* Support for custom DaprClient behaviors |
||||
|
|
||||
|
## Configuration Options |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"Dapr": { |
||||
|
"Client": { |
||||
|
"DaprApiToken": "your-api-token", // Optional, Dapr API Token |
||||
|
"HttpEndpoint": "http://localhost:3500", // Optional, HTTP endpoint |
||||
|
"GrpcEndpoint": "http://localhost:50001", // Optional, gRPC endpoint |
||||
|
"JsonSerializerOptions": { // Optional, JSON serialization options |
||||
|
"PropertyNamingPolicy": "CamelCase", |
||||
|
"PropertyNameCaseInsensitive": true, |
||||
|
"WriteIndented": true, |
||||
|
"DefaultIgnoreCondition": "WhenWritingNull" |
||||
|
}, |
||||
|
"GrpcChannelOptions": { // Optional, gRPC channel options |
||||
|
"Credentials": "Insecure", |
||||
|
"MaxReceiveMessageSize": 1048576, |
||||
|
"MaxSendMessageSize": 1048576 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Basic Usage |
||||
|
|
||||
|
Module reference as needed: |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpDaprModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Create a DaprClient |
||||
|
context.Services.AddDaprClient(); |
||||
|
|
||||
|
// Create a named DaprClient |
||||
|
context.Services.AddDaprClient("__DaprClient"); |
||||
|
|
||||
|
// Configure DaprClient options |
||||
|
Configure<DaprClientFactoryOptions>(options => |
||||
|
{ |
||||
|
options.HttpEndpoint = "http://localhost:3500"; |
||||
|
options.GrpcEndpoint = "http://localhost:50001"; |
||||
|
options.DaprApiToken = "your-api-token"; |
||||
|
|
||||
|
// Add DaprClient configuration actions |
||||
|
options.DaprClientActions.Add(client => |
||||
|
{ |
||||
|
// Configure DaprClient instance |
||||
|
}); |
||||
|
|
||||
|
// Add DaprClientBuilder configuration actions |
||||
|
options.DaprClientBuilderActions.Add(builder => |
||||
|
{ |
||||
|
// Configure DaprClientBuilder |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Advanced Usage |
||||
|
|
||||
|
### 1. Configure DaprClient |
||||
|
|
||||
|
```csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Configure named DaprClient |
||||
|
context.Services.AddDaprClient("CustomClient", builder => |
||||
|
{ |
||||
|
// Configure HTTP endpoint |
||||
|
builder.UseHttpEndpoint("http://localhost:3500"); |
||||
|
|
||||
|
// Configure gRPC endpoint |
||||
|
builder.UseGrpcEndpoint("http://localhost:50001"); |
||||
|
|
||||
|
// Configure API Token |
||||
|
builder.UseDaprApiToken("your-api-token"); |
||||
|
|
||||
|
// Configure JSON serialization options |
||||
|
builder.UseJsonSerializerOptions(new JsonSerializerOptions |
||||
|
{ |
||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
||||
|
PropertyNameCaseInsensitive = true |
||||
|
}); |
||||
|
|
||||
|
// Configure gRPC channel options |
||||
|
builder.UseGrpcChannelOptions(new GrpcChannelOptions |
||||
|
{ |
||||
|
MaxReceiveMessageSize = 1024 * 1024, |
||||
|
MaxSendMessageSize = 1024 * 1024 |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. Using DaprClient |
||||
|
|
||||
|
```csharp |
||||
|
public class YourService |
||||
|
{ |
||||
|
private readonly IDaprClientFactory _daprClientFactory; |
||||
|
|
||||
|
public YourService(IDaprClientFactory daprClientFactory) |
||||
|
{ |
||||
|
_daprClientFactory = daprClientFactory; |
||||
|
} |
||||
|
|
||||
|
public async Task InvokeMethodAsync() |
||||
|
{ |
||||
|
// Use default client |
||||
|
var defaultClient = _daprClientFactory.CreateClient(); |
||||
|
|
||||
|
// Use named client |
||||
|
var namedClient = _daprClientFactory.CreateClient("CustomClient"); |
||||
|
|
||||
|
// Invoke service method |
||||
|
var response = await defaultClient.InvokeMethodAsync<OrderDto>( |
||||
|
HttpMethod.Get, |
||||
|
"order-service", // Target service ID |
||||
|
"api/orders/1", // Method path |
||||
|
new { id = 1 } // Request parameters |
||||
|
); |
||||
|
|
||||
|
// Publish event |
||||
|
await defaultClient.PublishEventAsync( |
||||
|
"pubsub", // Pub/sub component name |
||||
|
"order-created", // Topic name |
||||
|
response // Event data |
||||
|
); |
||||
|
|
||||
|
// Save state |
||||
|
await defaultClient.SaveStateAsync( |
||||
|
"statestore", // State store component name |
||||
|
"order-1", // Key |
||||
|
response // Value |
||||
|
); |
||||
|
|
||||
|
// Get state |
||||
|
var state = await defaultClient.GetStateAsync<OrderDto>( |
||||
|
"statestore", // State store component name |
||||
|
"order-1" // Key |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. Custom DaprClient Behavior |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomDaprClientBehavior |
||||
|
{ |
||||
|
public void Configure(DaprClient client) |
||||
|
{ |
||||
|
// Configure custom behavior |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Register in module |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<DaprClientFactoryOptions>(options => |
||||
|
{ |
||||
|
options.DaprClientActions.Add(client => |
||||
|
{ |
||||
|
new CustomDaprClientBehavior().Configure(client); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Important Notes |
||||
|
|
||||
|
* DaprClient instances are thread-safe, singleton pattern is recommended |
||||
|
* Named DaprClients can have different configurations, suitable for scenarios requiring connections to different Dapr Sidecars |
||||
|
* Configuration changes require recreating the DaprClient instance to take effect |
||||
|
* Pay attention to performance and resource consumption when configuring gRPC channels |
||||
|
* JSON serialization options affect all requests using that DaprClient |
||||
|
* API Tokens should be managed through secure configuration management systems |
||||
|
* Recommended to use different named DaprClients for different microservices |
||||
|
* Configure appropriate timeout and retry policies in production environments |
||||
|
|
||||
|
[查看中文](README.md) |
||||
@ -0,0 +1,256 @@ |
|||||
|
# LINGYUN.Abp.DistributedLocking.Dapr |
||||
|
|
||||
|
An ABP distributed locking implementation based on the Dapr distributed lock API. This module provides seamless integration with Dapr's distributed locking service, supporting cross-service and cross-instance locking capabilities. |
||||
|
|
||||
|
Reference: [Dapr Distributed Lock API](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/) |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
* Integration with ABP distributed locking system |
||||
|
* Support for custom lock resource owner identification |
||||
|
* Configurable lock timeout duration |
||||
|
* Automatic lock release support |
||||
|
* Multiple lock storage component support |
||||
|
* Lock acquisition and release event notifications |
||||
|
* Distributed lock health check support |
||||
|
|
||||
|
## Configuration Options |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"DistributedLocking": { |
||||
|
"Dapr": { |
||||
|
"StoreName": "lockstore", // Storage name defined in Dapr component |
||||
|
"DefaultIdentifier": "dapr-lock-owner", // Default lock resource owner identifier |
||||
|
"DefaultTimeout": "00:00:30" // Default lock timeout (30 seconds) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Basic Usage |
||||
|
|
||||
|
### 1. Module Configuration |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpDistributedLockingDaprModule))] |
||||
|
public class YourProjectModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Basic configuration |
||||
|
Configure<AbpDistributedLockingDaprOptions>(options => |
||||
|
{ |
||||
|
options.StoreName = "redis-lock"; // Use Redis as lock storage |
||||
|
options.DefaultIdentifier = "my-service"; // Custom lock owner identifier |
||||
|
options.DefaultTimeout = TimeSpan.FromMinutes(1); // Set default timeout to 1 minute |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. Basic Usage |
||||
|
|
||||
|
```csharp |
||||
|
public class OrderService |
||||
|
{ |
||||
|
private readonly IDistributedLockProvider _lockProvider; |
||||
|
|
||||
|
public OrderService(IDistributedLockProvider lockProvider) |
||||
|
{ |
||||
|
_lockProvider = lockProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task ProcessOrderAsync(string orderId) |
||||
|
{ |
||||
|
// Try to acquire lock |
||||
|
using (var handle = await _lockProvider.TryAcquireAsync($"order:{orderId}")) |
||||
|
{ |
||||
|
if (handle != null) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// Execute business logic that requires locking |
||||
|
await ProcessOrderInternalAsync(orderId); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
// Handle exception |
||||
|
_logger.LogError(ex, "Error occurred while processing order"); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new ConcurrencyException("Order is being processed by another process"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. Advanced Usage |
||||
|
|
||||
|
```csharp |
||||
|
public class InventoryService |
||||
|
{ |
||||
|
private readonly IDistributedLockProvider _lockProvider; |
||||
|
private readonly ILogger<InventoryService> _logger; |
||||
|
|
||||
|
public InventoryService( |
||||
|
IDistributedLockProvider lockProvider, |
||||
|
ILogger<InventoryService> logger) |
||||
|
{ |
||||
|
_lockProvider = lockProvider; |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
public async Task UpdateInventoryAsync(string productId, int quantity) |
||||
|
{ |
||||
|
// Custom lock configuration |
||||
|
var lockOptions = new DistributedLockOptions |
||||
|
{ |
||||
|
Timeout = TimeSpan.FromSeconds(10), // Custom timeout |
||||
|
RetryDelay = TimeSpan.FromMilliseconds(100) // Retry delay |
||||
|
}; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
using (var handle = await _lockProvider.TryAcquireAsync( |
||||
|
$"inventory:{productId}", |
||||
|
lockOptions)) |
||||
|
{ |
||||
|
if (handle == null) |
||||
|
{ |
||||
|
_logger.LogWarning("Unable to acquire inventory lock for product ID: {ProductId}", productId); |
||||
|
throw new ConcurrencyException("Unable to acquire inventory lock"); |
||||
|
} |
||||
|
|
||||
|
// Execute inventory update operation |
||||
|
await UpdateInventoryInternalAsync(productId, quantity); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) when (ex is not ConcurrencyException) |
||||
|
{ |
||||
|
_logger.LogError(ex, "Error occurred while updating inventory"); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Component Configuration |
||||
|
|
||||
|
### Redis Lock Store Configuration Example |
||||
|
|
||||
|
```yaml |
||||
|
apiVersion: dapr.io/v1alpha1 |
||||
|
kind: Component |
||||
|
metadata: |
||||
|
name: redis-lock # Corresponds to StoreName configuration |
||||
|
spec: |
||||
|
type: lock.redis |
||||
|
version: v1 |
||||
|
metadata: |
||||
|
- name: redisHost |
||||
|
value: localhost:6379 |
||||
|
- name: redisPassword |
||||
|
value: "" |
||||
|
- name: enableTLS |
||||
|
value: false |
||||
|
- name: maxRetries |
||||
|
value: 5 |
||||
|
- name: maxRetryBackoff |
||||
|
value: 5s |
||||
|
``` |
||||
|
|
||||
|
### Consul Lock Store Configuration Example |
||||
|
|
||||
|
```yaml |
||||
|
apiVersion: dapr.io/v1alpha1 |
||||
|
kind: Component |
||||
|
metadata: |
||||
|
name: consul-lock |
||||
|
spec: |
||||
|
type: lock.consul |
||||
|
version: v1 |
||||
|
metadata: |
||||
|
- name: host |
||||
|
value: localhost:8500 |
||||
|
- name: sessionTTL |
||||
|
value: 10 |
||||
|
- name: scheme |
||||
|
value: http |
||||
|
``` |
||||
|
|
||||
|
## Core Interfaces |
||||
|
|
||||
|
### ILockOwnerFinder |
||||
|
|
||||
|
Interface for providing lock resource owner identification. |
||||
|
|
||||
|
```csharp |
||||
|
public interface ILockOwnerFinder |
||||
|
{ |
||||
|
string GetOwner(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The default implementation `LockOwnerFinder`: |
||||
|
1. Primarily uses the current user ID as the lock owner identifier |
||||
|
2. Falls back to the configured `DefaultIdentifier` if no user is logged in |
||||
|
|
||||
|
### Custom Lock Owner Identifier Implementation |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomLockOwnerFinder : ILockOwnerFinder |
||||
|
{ |
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
|
||||
|
public CustomLockOwnerFinder(ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
_currentTenant = currentTenant; |
||||
|
} |
||||
|
|
||||
|
public string GetOwner() |
||||
|
{ |
||||
|
// Use combination of tenant ID and machine name as lock owner identifier |
||||
|
return $"{_currentTenant.Id ?? "host"}-{Environment.MachineName}"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Register custom implementation |
||||
|
context.Services.AddTransient<ILockOwnerFinder, CustomLockOwnerFinder>(); |
||||
|
``` |
||||
|
|
||||
|
## Best Practices |
||||
|
|
||||
|
1. **Set Appropriate Timeout Duration** |
||||
|
- Set timeout based on expected execution time of business operations |
||||
|
- Avoid setting excessively long timeouts to prevent deadlocks |
||||
|
|
||||
|
2. **Proper Lock Granularity** |
||||
|
- Keep lock scope as small as possible, only lock necessary resources |
||||
|
- Avoid holding locks for extended periods, release promptly |
||||
|
|
||||
|
3. **Exception Handling** |
||||
|
- Always use locks within using blocks |
||||
|
- Handle lock acquisition failures appropriately |
||||
|
- Log critical lock operations |
||||
|
|
||||
|
4. **Performance Optimization** |
||||
|
- Use appropriate storage components |
||||
|
- Configure suitable retry policies |
||||
|
- Monitor lock usage |
||||
|
|
||||
|
## Important Notes |
||||
|
|
||||
|
* Ensure Dapr Sidecar is properly configured and running |
||||
|
* Distributed lock component must be correctly defined in Dapr configuration |
||||
|
* Set appropriate lock timeouts to avoid deadlocks |
||||
|
* Handle lock acquisition failures properly |
||||
|
* Consider performance impact in high-concurrency scenarios |
||||
|
* Configure health checks for lock components |
||||
|
* Add logging for important operations |
||||
|
|
||||
|
[查看中文](README.md) |
||||
@ -1,38 +1,256 @@ |
|||||
# LINGYUN.Abp.DistributedLocking.Dapr |
# LINGYUN.Abp.DistributedLocking.Dapr |
||||
|
|
||||
Abp分布式锁的Dapr实现 |
基于Dapr分布式锁API的ABP分布式锁实现。该模块提供了与Dapr分布式锁服务的无缝集成,支持跨服务、跨实例的分布式锁定功能。 |
||||
|
|
||||
See: https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/ |
参考文档: [Dapr Distributed Lock API](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/) |
||||
|
|
||||
## 配置使用 |
## 功能特性 |
||||
|
|
||||
模块按需引用 |
* 与ABP分布式锁系统集成 |
||||
|
* 支持自定义锁资源拥有者标识 |
||||
|
* 支持可配置的锁定超时时间 |
||||
|
* 支持锁资源自动释放 |
||||
|
* 支持多种锁存储组件 |
||||
|
* 支持锁获取和释放的事件通知 |
||||
|
* 支持分布式锁的健康检查 |
||||
|
|
||||
|
## 配置选项 |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"DistributedLocking": { |
||||
|
"Dapr": { |
||||
|
"StoreName": "lockstore", // Dapr组件中定义的存储名称 |
||||
|
"DefaultIdentifier": "dapr-lock-owner", // 默认锁资源拥有者标识 |
||||
|
"DefaultTimeout": "00:00:30" // 默认锁定超时时间(30秒) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 基础使用 |
||||
|
|
||||
|
### 1. 模块配置 |
||||
|
|
||||
```csharp |
```csharp |
||||
[DependsOn(typeof(AbpDistributedLockingDaprModule))] |
[DependsOn(typeof(AbpDistributedLockingDaprModule))] |
||||
public class YouProjectModule : AbpModule |
public class YourProjectModule : AbpModule |
||||
{ |
{ |
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
{ |
{ |
||||
|
// 基础配置 |
||||
Configure<AbpDistributedLockingDaprOptions>(options => |
Configure<AbpDistributedLockingDaprOptions>(options => |
||||
{ |
{ |
||||
options.StoreName = "store-name"; |
options.StoreName = "redis-lock"; // 使用Redis作为锁存储 |
||||
options.DefaultIdentifier = "default-owner-id"; |
options.DefaultIdentifier = "my-service"; // 自定义锁拥有者标识 |
||||
options.DefaultTimeout = TimeSpan.FromSeconds(30); |
options.DefaultTimeout = TimeSpan.FromMinutes(1); // 设置默认超时时间为1分钟 |
||||
}); |
}); |
||||
} |
} |
||||
} |
} |
||||
``` |
``` |
||||
|
|
||||
## 配置说明 |
### 2. 基本用法 |
||||
|
|
||||
|
```csharp |
||||
|
public class OrderService |
||||
|
{ |
||||
|
private readonly IDistributedLockProvider _lockProvider; |
||||
|
|
||||
|
public OrderService(IDistributedLockProvider lockProvider) |
||||
|
{ |
||||
|
_lockProvider = lockProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task ProcessOrderAsync(string orderId) |
||||
|
{ |
||||
|
// 尝试获取锁 |
||||
|
using (var handle = await _lockProvider.TryAcquireAsync($"order:{orderId}")) |
||||
|
{ |
||||
|
if (handle != null) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// 执行需要加锁的业务逻辑 |
||||
|
await ProcessOrderInternalAsync(orderId); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
// 处理异常 |
||||
|
_logger.LogError(ex, "处理订单时发生错误"); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new ConcurrencyException("订单正在被其他进程处理"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. 高级用法 |
||||
|
|
||||
|
```csharp |
||||
|
public class InventoryService |
||||
|
{ |
||||
|
private readonly IDistributedLockProvider _lockProvider; |
||||
|
private readonly ILogger<InventoryService> _logger; |
||||
|
|
||||
|
public InventoryService( |
||||
|
IDistributedLockProvider lockProvider, |
||||
|
ILogger<InventoryService> logger) |
||||
|
{ |
||||
|
_lockProvider = lockProvider; |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
public async Task UpdateInventoryAsync(string productId, int quantity) |
||||
|
{ |
||||
|
// 自定义锁配置 |
||||
|
var lockOptions = new DistributedLockOptions |
||||
|
{ |
||||
|
Timeout = TimeSpan.FromSeconds(10), // 自定义超时时间 |
||||
|
RetryDelay = TimeSpan.FromMilliseconds(100) // 重试延迟 |
||||
|
}; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
using (var handle = await _lockProvider.TryAcquireAsync( |
||||
|
$"inventory:{productId}", |
||||
|
lockOptions)) |
||||
|
{ |
||||
|
if (handle == null) |
||||
|
{ |
||||
|
_logger.LogWarning("无法获取库存锁,产品ID: {ProductId}", productId); |
||||
|
throw new ConcurrencyException("无法获取库存锁"); |
||||
|
} |
||||
|
|
||||
|
// 执行库存更新操作 |
||||
|
await UpdateInventoryInternalAsync(productId, quantity); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) when (ex is not ConcurrencyException) |
||||
|
{ |
||||
|
_logger.LogError(ex, "更新库存时发生错误"); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 组件配置 |
||||
|
|
||||
|
### Redis锁存储配置示例 |
||||
|
|
||||
|
```yaml |
||||
|
apiVersion: dapr.io/v1alpha1 |
||||
|
kind: Component |
||||
|
metadata: |
||||
|
name: redis-lock # 对应StoreName配置 |
||||
|
spec: |
||||
|
type: lock.redis |
||||
|
version: v1 |
||||
|
metadata: |
||||
|
- name: redisHost |
||||
|
value: localhost:6379 |
||||
|
- name: redisPassword |
||||
|
value: "" |
||||
|
- name: enableTLS |
||||
|
value: false |
||||
|
- name: maxRetries |
||||
|
value: 5 |
||||
|
- name: maxRetryBackoff |
||||
|
value: 5s |
||||
|
``` |
||||
|
|
||||
|
### Consul锁存储配置示例 |
||||
|
|
||||
|
```yaml |
||||
|
apiVersion: dapr.io/v1alpha1 |
||||
|
kind: Component |
||||
|
metadata: |
||||
|
name: consul-lock |
||||
|
spec: |
||||
|
type: lock.consul |
||||
|
version: v1 |
||||
|
metadata: |
||||
|
- name: host |
||||
|
value: localhost:8500 |
||||
|
- name: sessionTTL |
||||
|
value: 10 |
||||
|
- name: scheme |
||||
|
value: http |
||||
|
``` |
||||
|
|
||||
|
## 核心接口 |
||||
|
|
||||
|
### ILockOwnerFinder |
||||
|
|
||||
|
提供锁资源持有者标识的接口。 |
||||
|
|
||||
|
```csharp |
||||
|
public interface ILockOwnerFinder |
||||
|
{ |
||||
|
string GetOwner(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
默认实现 `LockOwnerFinder` 会: |
||||
|
1. 优先使用当前用户ID作为锁拥有者标识 |
||||
|
2. 如果用户未登录,则使用配置的 `DefaultIdentifier` |
||||
|
|
||||
|
### 自定义锁拥有者标识实现 |
||||
|
|
||||
|
```csharp |
||||
|
public class CustomLockOwnerFinder : ILockOwnerFinder |
||||
|
{ |
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
|
||||
|
public CustomLockOwnerFinder(ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
_currentTenant = currentTenant; |
||||
|
} |
||||
|
|
||||
|
public string GetOwner() |
||||
|
{ |
||||
|
// 使用租户ID和机器名称组合作为锁拥有者标识 |
||||
|
return $"{_currentTenant.Id ?? "host"}-{Environment.MachineName}"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 注册自定义实现 |
||||
|
context.Services.AddTransient<ILockOwnerFinder, CustomLockOwnerFinder>(); |
||||
|
``` |
||||
|
|
||||
|
## 最佳实践 |
||||
|
|
||||
|
1. **合理设置超时时间** |
||||
|
- 根据业务操作的预期执行时间设置合适的超时时间 |
||||
|
- 避免设置过长的超时时间,以防止死锁 |
||||
|
|
||||
|
2. **正确的锁粒度** |
||||
|
- 锁的范围应该尽可能小,只锁定必要的资源 |
||||
|
- 避免长时间持有锁,及时释放 |
||||
|
|
||||
|
3. **异常处理** |
||||
|
- 始终在 using 块中使用锁 |
||||
|
- 妥善处理锁获取失败的情况 |
||||
|
- 记录关键的锁操作日志 |
||||
|
|
||||
* AbpDistributedLockingDaprOptions.StoreName 在dapr component文件中定义的metadata name,默认: lockstore; |
4. **性能优化** |
||||
* AbpDistributedLockingDaprOptions.DefaultIdentifier 默认锁资源拥有者标识,默认: dapr-lock-owner; |
- 使用合适的存储组件 |
||||
* AbpDistributedLockingDaprOptions.DefaultTimeout 默认锁定超时时间,默认: 30s. |
- 配置适当的重试策略 |
||||
|
- 监控锁的使用情况 |
||||
|
|
||||
## 接口说明 |
## 注意事项 |
||||
|
|
||||
[ILockOwnerFinder](./LINGYUN/Abp/DistributedLocking/Dapr/ILockOwnerFinder), 提供锁资源持有者标识 |
* 确保Dapr Sidecar已正确配置并运行 |
||||
默认实现 [LockOwnerFinder](./LINGYUN/Abp/DistributedLocking/Dapr/LockOwnerFinder), 获取用户标识,如果不存在,返回DefaultIdentifier |
* 分布式锁组件需要在Dapr配置中正确定义 |
||||
|
* 合理设置锁的超时时间,避免死锁 |
||||
|
* 正确处理锁获取失败的情况 |
||||
|
* 在高并发场景下注意性能影响 |
||||
|
* 建议配置锁组件的健康检查 |
||||
|
* 重要操作建议添加日志记录 |
||||
|
|
||||
## 其他 |
[查看英文](README.EN.md) |
||||
|
|||||
Loading…
Reference in new issue