Skip to content
This repository has been archived by the owner on Aug 1, 2022. It is now read-only.

Commit

Permalink
fix(Caller): Fix IRequestMessage life cycle (#68)
Browse files Browse the repository at this point in the history
* fix(Caller): Fix IRequestMessage life cycle

* docs(Caller): Improve Caller documentation

* chore: Adjust AddCaller

* test(Caller): Add RequestMessage unit test
  • Loading branch information
zhenlei520 committed Jun 10, 2022
1 parent 5f300fa commit fed40e5
Show file tree
Hide file tree
Showing 16 changed files with 580 additions and 42 deletions.
3 changes: 2 additions & 1 deletion src/Caller/Masa.Utils.Caller.Core/ICallerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ Task<string> GetStringAsync(
CancellationToken cancellationToken = default);

Task<string> GetStringAsync<TRequest>(
string? methodName, TRequest data,
string? methodName,
TRequest data,
bool autoThrowUserFriendlyException = true,
CancellationToken cancellationToken = default) where TRequest : class;

Expand Down
36 changes: 36 additions & 0 deletions src/Caller/Masa.Utils.Caller.Core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[](README.zh-CN.md) | EN

## Masa.Utils.Caller.Core

Masa.Utils.Caller.Core is the basic class library of Caller, which provides the abstraction of the following capabilities

* `ICallerFactory`: Factory for creating `CallerProvider` (Singleton)
* `ICallerProvider`: Provides `Post`, `Delete`, `Patch`, `Put`, `Get`, `Send` capabilities (Scoped)
* `IRequestMessage`: Provides the ability to process request data (default implementation [`JsonRequestMessage`](./JsonRequestMessage.cs)) (Singleton)
* `IResponseMessage`: Provides the ability to handle response data (default implementation [`DefaultResponseMessage`](./DefaultResponseMessage.cs)) (Singleton)
* `ITypeConvertProvider`: Provides the ability to convert types, support for `Get` requests of `ICallerProvider` (Singleton)

## Summarize

`Masa.Utils.Caller.Core` is the basic class library of Caller, but it cannot be used alone. Currently, Caller supports two implementations:

* Implementation based on HttpClient: [Masa.Utils.Caller.HttpClient](../Masa.Utils.Caller.HttpClient/README.md)
* Implementation based on DaprClient: [Masa.Utils.Caller.DaprClient](../Masa.Utils.Caller.DaprClient/README.md)

> Q: What should I do if the callee uses xml instead of json?
>
> A: Rewrite IRequestMessage and add the custom RequestMessage to the IServiceCollection before calling AddCaller
```` C#
services.AddSingleton<IRequestMessage, XmlRequestMessage>();
services.AddCaller();
````

> Q: If you want to handle custom StatusCode and throw exception information
>
> A: Rewrite IResponseMessage, add custom ResponseMessage to IServiceCollection before calling AddCaller

```` C#
services.AddSingleton<IResponseMessage, CustomizeResponseMessage>();
services.AddCaller();
````
36 changes: 36 additions & 0 deletions src/Caller/Masa.Utils.Caller.Core/README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
中 | [EN](README.md)

## Masa.Utils.Caller.Core

Masa.Utils.Caller.Core是Caller的基础类库,提供了以下能力的抽象

* `ICallerFactory`: 工厂,用于创建`CallerProvider` (Singleton)
* `ICallerProvider`: 提供`Post``Delete``Patch``Put``Get``Send`的能力 (Scoped)
* `IRequestMessage`: 提供对请求数据处理的能力 (默认实现[`JsonRequestMessage`](./JsonRequestMessage.cs)) (Singleton)
* `IResponseMessage`: 提供对响应数据处理的能力 (默认实现[`DefaultResponseMessage`](./DefaultResponseMessage.cs)) (Singleton)
* `ITypeConvertProvider`: 提供类型转换的能力,为`ICallerProvider``Get`请求支撑 (Singleton)

## 总结

`Masa.Utils.Caller.Core`是Caller的基础类库,但不能单独使用,目前Caller支持了两种实现方式:

* 基于HttpClient的实现: [Masa.Utils.Caller.HttpClient](../Masa.Utils.Caller.HttpClient/README.zh-CN.md)
* 基于DaprClient的实现: [Masa.Utils.Caller.DaprClient](../Masa.Utils.Caller.DaprClient/README.zh-CN.md)

> Q: 如果被调用方使用的是数据格式为xml,而不是json,我应该怎么做?
>
> A: 重写IRequestMessage,在调用AddCaller之前先将自定义的RequestMessage添加到IServiceCollection中
``` C#
services.AddSingleton<IRequestMessage, XmlRequestMessage>();
services.AddCaller();
```

> Q: 如果希望处理自定义的StatusCode,并抛出异常信息
>
> A: 重写IResponseMessage,在调用AddCaller之前先将自定义的ResponseMessage添加到IServiceCollection

``` C#
services.AddSingleton<IResponseMessage, CustomizeResponseMessage>();
services.AddCaller();
```
48 changes: 20 additions & 28 deletions src/Caller/Masa.Utils.Caller.Core/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,36 @@ public static IServiceCollection AddCaller(this IServiceCollection services, Act
CallerOptions callerOption = new CallerOptions(services);
options.Invoke(callerOption);

services.TryAddSingleton<ICallerFactory, DefaultCallerFactory>();
services.TryAdd(new ServiceDescriptor(typeof(IRequestMessage), _ => new JsonRequestMessage(callerOption.JsonSerializerOptions), callerOption.CallerLifetime));
services.TryAddSingleton<IResponseMessage, DefaultResponseMessage>();
services.TryAddSingleton<ICallerFactory>(serviceProvider => new DefaultCallerFactory(serviceProvider, callerOption));
services.TryAddSingleton<IRequestMessage>(_ => new JsonRequestMessage(callerOption.JsonSerializerOptions));
services.TryAddSingleton<IResponseMessage>(serviceProvider
=> new DefaultResponseMessage(callerOption, serviceProvider.GetService<ILogger<DefaultResponseMessage>>()));
services.TryAddScoped(serviceProvider => serviceProvider.GetRequiredService<ICallerFactory>().CreateClient());

services.TryAddSingleton<ITypeConvertProvider, DefaultTypeConvertProvider>();
services.AddAutomaticCaller(callerOption);
services.TryOrUpdateCallerOptions(callerOption);
CheckCallerOptions(callerOption);
return services;
}

private static IServiceCollection TryOrUpdateCallerOptions(this IServiceCollection services, CallerOptions options)
private static void CheckCallerOptions(CallerOptions options)
{
services.TryAddSingleton(new CallerOptions(options.Services));
var serviceProvider = services.BuildServiceProvider();
var callerOptions = serviceProvider.GetRequiredService<CallerOptions>();

options.Callers.ForEach(caller =>
if (options.Callers.GroupBy(r => r.Name).Any(x => x.Count() > 1))
{
if (callerOptions.Callers.Any(relation => relation.Name == caller.Name))
throw new ArgumentException(
$"The caller name already exists, please change the name, the repeat name is [{caller.Name}]");
if (callerOptions.Callers.Any(relation => relation.IsDefault && caller.IsDefault))
{
string errorCallerNames = string.Join("", callerOptions.Callers
.Where(relation => relation.IsDefault)
.Select(relation => relation.Name)
.Concat(options.Callers.Where(relation => relation.IsDefault).Select(relation => relation.Name))
.Distinct());
throw new ArgumentException(
$"There can only be at most one default Caller Provider, and now the following Caller Providers are found to be default: {errorCallerNames}");
}
var callerName = options.Callers.GroupBy(r => r.Name).Where(x => x.Count() > 1).Select(r => r.Key).FirstOrDefault();
throw new ArgumentException($"The caller name already exists, please change the name, the repeat name is [{callerName}]");
}

callerOptions.Callers.Add(caller);
});

return services;
if (options.Callers.Where(r => r.IsDefault).GroupBy(r => r.IsDefault).Any(x => x.Count() > 1))
{
string errorCallerNames = string.Join("", options.Callers
.Where(relation => relation.IsDefault)
.Select(relation => relation.Name)
.Concat(options.Callers.Where(relation => relation.IsDefault).Select(relation => relation.Name))
.Distinct());
throw new ArgumentException(
$"There can only be at most one default Caller Provider, and now the following Caller Providers are found to be default: {errorCallerNames}");
}
}

private static void AddAutomaticCaller(this IServiceCollection services, CallerOptions callerOptions)
Expand Down
101 changes: 101 additions & 0 deletions src/Caller/Masa.Utils.Caller.DaprClient/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[](README.zh-CN.md) | EN

## Masa.Utils.Caller.DaprClient

## Example:

````c#
Install-Package Masa.Utils.Caller.DaprClient
````

### Basic usage:

1. Modify `Program.cs`

```` C#
builder.Services.AddCaller(options =>
{
options.UseDapr(clientBuilder =>
{
clientBuilder.Name = "UserCaller";// The alias of the current Caller, when there is only one Provider, you can not assign a value to Name
clientBuilder.AppId = "<Replace-You-Dapr-AppID>" ;//AppID of the callee dapr
});
});
````

2. How to use:

```` C#
app.MapGet("/Test/User/Hello", ([FromServices] ICallerProvider userCallerProvider, string name)
=> userCallerProvider.GetAsync<string>($"/Hello", new { Name = name }));
````

> The interface address of the complete request is: http://localhost:3500/v1.0/invoke/<Replace-You-Dapr-AppID>/method/Hello?Name={name}
3. When there are multiple DaprClients, modify `Program.cs`

```` C#
builder.Services.AddCaller(options =>
{
options.UseDapr(clientBuilder =>
{
clientBuilder.Name = "UserCaller";
clientBuilder.AppId = "<Replace-You-Dapr-AppID>" ;//AppID of the callee User service Dapr
});
options.UseDapr(clientBuilder =>
{
clientBuilder.Name = "OrderCaller";
clientBuilder.AppId = "<Replace-You-Dapr-AppID>" ;//AppID of the callee Order service Dapr
});
});
````

4. How to use UserCaller or OrderCaller

```` C#
app.MapGet("/Test/User/Hello", ([FromServices] ICallerProvider userCallerProvider, string name)
=> userCallerProvider.GetAsync<string>($"/Hello", new { Name = name }));


app.MapGet("/Test/Order/Hello", ([FromServices] ICallerFactory callerFactory, string name) =>
{
var callerProvider = callerFactory.CreateClient("OrderCaller");
return callerProvider.GetAsync<string>($"/Hello", new { Name = name });
});
````

> When multiple Callers are added, how to get the specified Caller?
>> Get the CallerProvider of the specified alias through the `CreateClient` method of `CallerFactory`
>
> Why doesn't `userCallerProvider` get the corresponding Caller through the `CreateClient` method of `CallerFactory`?
>> If no default ICallerProvider is specified, the default CallerProvider is the first one added in the `AddCaller` method

#### Recommended usage

1. Modify `Program.cs`

```` C#
builder.Services.AddCaller();
````

2. Add a new class `UserCaller`

```` C#
public class UserCaller: DaprCallerBase
{
protected override string AppId { get; set; } = "<Replace-You-UserService-Dapr-AppID>";

public HttpCaller(IServiceProvider serviceProvider) : base(serviceProvider)
{
}

public Task<string> HelloAsync(string name) => CallerProvider.GetStringAsync($"/Hello", new { Name = name });
}
````

3. How to use UserCaller

```` C#
app.MapGet("/Test/User/Hello", ([FromServices] UserCaller caller, string name)
=> caller.HelloAsync(name));
````
101 changes: 101 additions & 0 deletions src/Caller/Masa.Utils.Caller.DaprClient/README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
中 | [EN](README.md)

## Masa.Utils.Caller.DaprClient

## 用例:

```c#
Install-Package Masa.Utils.Caller.DaprClient
```

### 基本用法:

1. 修改`Program.cs`

``` C#
builder.Services.AddCaller(options =>
{
options.UseDapr(clientBuilder =>
{
clientBuilder.Name = "UserCaller";// 当前Caller的别名,仅存在一个Provider时,可以不对Name赋值
clientBuilder.AppId = "<Replace-You-Dapr-AppID>" ;//被调用方dapr的AppID
});
});
```

2. 如何使用:

``` C#
app.MapGet("/Test/User/Hello", ([FromServices] ICallerProvider userCallerProvider, string name)
=> userCallerProvider.GetAsync<string>($"/Hello", new { Name = name }));
```

> 完整请求的接口地址是:http://localhost:3500/v1.0/invoke/<Replace-You-Dapr-AppID>/method/Hello?Name={name}
3. 当存在多个DaprClient时,则修改`Program.cs`为

``` C#
builder.Services.AddCaller(options =>
{
options.UseDapr(clientBuilder =>
{
clientBuilder.Name = "UserCaller";
clientBuilder.AppId = "<Replace-You-Dapr-AppID>" ;//被调用方User服务Dapr的AppID
});
options.UseDapr(clientBuilder =>
{
clientBuilder.Name = "OrderCaller";
clientBuilder.AppId = "<Replace-You-Dapr-AppID>" ;//被调用方Order服务Dapr的AppID
});
});
```

4. 如何使用UserCallerOrderCaller

``` C#
app.MapGet("/Test/User/Hello", ([FromServices] ICallerProvider userCallerProvider, string name)
=> userCallerProvider.GetAsync<string>($"/Hello", new { Name = name }));


app.MapGet("/Test/Order/Hello", ([FromServices] ICallerFactory callerFactory, string name) =>
{
var callerProvider = callerFactory.CreateClient("OrderCaller");
return callerProvider.GetAsync<string>($"/Hello", new { Name = name });
});
```

> 当多个Caller被添加时,如何获取指定的Caller
>> 通过`CallerFactory`的`CreateClient`方法得到指定别名的CallerProvider
>
> 为什么`userCallerProvider`没有通过`CallerFactory`的`CreateClient`方法得到对应的Caller
>> 如果未指定默认的ICallerProvider,则在`AddCaller`方法中第一个被添加的就是默认的CallerProvider

#### 推荐用法

1. 修改`Program.cs`

``` C#
builder.Services.AddCaller();
```

2. 新增加类`UserCaller`

``` C#
public class UserCaller: DaprCallerBase
{
protected override string AppId { get; set; } = "<Replace-You-UserService-Dapr-AppID>";

public HttpCaller(IServiceProvider serviceProvider) : base(serviceProvider)
{
}

public Task<string> HelloAsync(string name) => CallerProvider.GetStringAsync($"/Hello", new { Name = name });
}
```

3. 如何使用UserCaller

``` C#
app.MapGet("/Test/User/Hello", ([FromServices] UserCaller caller, string name)
=> caller.HelloAsync(name));
```
12 changes: 6 additions & 6 deletions src/Caller/Masa.Utils.Caller.HttpClient/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ Install-Package Masa.Utils.Caller.HttpClient

```` C#
app.MapGet("/Test/User/Hello", ([FromServices] ICallerProvider userCallerProvider, string name)
=> userCallerProvider.GetAsync<string>($"/Hello/{name}"));
=> userCallerProvider.GetAsync<string>($"/Hello", new { Name = name }));
````

> The interface address of the complete request is: http://localhost:5000/Check/Healthy
> The interface address of the complete request is: http://localhost:5000/Hello?Name={name}
3. When there are multiple HttpClients, modify `Program.cs` as
3. When there are multiple HttpClients, modify `Program.cs`

```` C#
builder.Services.AddCaller(options =>
Expand All @@ -54,13 +54,13 @@ Install-Package Masa.Utils.Caller.HttpClient

```` C#
app.MapGet("/Test/User/Hello", ([FromServices] ICallerProvider userCallerProvider, string name)
=> userCallerProvider.GetAsync<string>($"/Hello/{name}"));
=> userCallerProvider.GetAsync<string>($"/Hello", new { Name = name }));


app.MapGet("/Test/Order/Hello", ([FromServices] ICallerFactory callerFactory, string name) =>
{
var callerProvider = callerFactory.CreateClient("OrderCaller");
return callerProvider.GetAsync<string>($"/Hello/{name}");
return callerProvider.GetAsync<string>($"/Hello", new { Name = name });
});
````

Expand Down Expand Up @@ -89,7 +89,7 @@ Install-Package Masa.Utils.Caller.HttpClient
{
}

public Task<string> HelloAsync(string name) => CallerProvider.GetStringAsync($"/Hello/{name}");
public Task<string> HelloAsync(string name) => CallerProvider.GetStringAsync($"/Hello", new { Name = name });

/// <summary>
/// There is no need to overload by default, and it can be overloaded when there are special requirements for httpClient
Expand Down
Loading

0 comments on commit fed40e5

Please sign in to comment.