Skip to content

Commit f723775

Browse files
authored
Merge pull request #161 from cnblogs/support-custom-error-for-cqrs-service-agent
feat: support custom error for cqrs service agent
2 parents 9d5a9c2 + 681b4da commit f723775

File tree

4 files changed

+63
-41
lines changed

4 files changed

+63
-41
lines changed

src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ protected virtual IActionResult CustomErrorCommandResponseMap<TError>(CommandRes
100100
private IActionResult MapErrorCommandResponseToCqrsResponse<TError>(CommandResponse<TError> response)
101101
where TError : Enumeration
102102
{
103+
if (response is { IsConcurrentError: true, LockAcquired: false })
104+
{
105+
return StatusCode(429);
106+
}
107+
103108
return BadRequest(response);
104109
}
105110

src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ private IResult HandleErrorCommandResponse(CommandResponse response, HttpContext
8989

9090
private static IResult HandleErrorCommandResponseWithCqrs(CommandResponse response)
9191
{
92-
return Results.BadRequest(response);
92+
if (response is { IsConcurrentError: true, LockAcquired: false })
93+
{
94+
return Results.StatusCode(429);
95+
}
96+
97+
return Results.BadRequest((object)response);
9398
}
9499

95100
private static IResult HandleErrorCommandResponseWithPlainText(CommandResponse response)

src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
using System.Net;
22
using System.Net.Http.Json;
33
using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;
4+
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
45
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
56

67
namespace Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent;
78

9+
/// <summary>
10+
/// Base Class for CQRS Service Agent.
11+
/// </summary>
12+
public abstract class CqrsServiceAgent : CqrsServiceAgent<ServiceAgentError>
13+
{
14+
/// <summary>
15+
/// Create a Cqrs service agent.
16+
/// </summary>
17+
/// <param name="httpClient">The underlying http client.</param>
18+
protected CqrsServiceAgent(HttpClient httpClient)
19+
: base(httpClient)
20+
{
21+
}
22+
}
23+
824
/// <summary>
925
/// Service Agent for CQRS
1026
/// </summary>
11-
public abstract class CqrsServiceAgent
27+
/// <typeparam name="TError">The type of error for this service.</typeparam>
28+
public abstract class CqrsServiceAgent<TError>
29+
where TError : Enumeration
1230
{
1331
/// <summary>
1432
/// The underlying <see cref="HttpClient"/>.
@@ -30,7 +48,7 @@ protected CqrsServiceAgent(HttpClient httpClient)
3048
/// <param name="url">The url.</param>
3149
/// <typeparam name="TResponse">Response type.</typeparam>
3250
/// <returns>The response.</returns>
33-
public async Task<CommandResponse<TResponse, ServiceAgentError>> DeleteCommandAsync<TResponse>(string url)
51+
public async Task<CommandResponse<TResponse, TError>> DeleteCommandAsync<TResponse>(string url)
3452
{
3553
var response = await HttpClient.DeleteAsync(url);
3654
return await HandleCommandResponseAsync<TResponse>(response);
@@ -40,7 +58,7 @@ public async Task<CommandResponse<TResponse, ServiceAgentError>> DeleteCommandAs
4058
/// Execute a command with DELETE method.
4159
/// </summary>
4260
/// <param name="url">The route of the API.</param>
43-
public async Task<CommandResponse<ServiceAgentError>> DeleteCommandAsync(string url)
61+
public async Task<CommandResponse<TError>> DeleteCommandAsync(string url)
4462
{
4563
var response = await HttpClient.DeleteAsync(url);
4664
return await HandleCommandResponseAsync(response);
@@ -50,7 +68,7 @@ public async Task<CommandResponse<ServiceAgentError>> DeleteCommandAsync(string
5068
/// Execute a command with POST method.
5169
/// </summary>
5270
/// <param name="url">The route of the API.</param>
53-
public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync(string url)
71+
public async Task<CommandResponse<TError>> PostCommandAsync(string url)
5472
{
5573
var response = await HttpClient.PostAsync(url, new StringContent(string.Empty));
5674
return await HandleCommandResponseAsync(response);
@@ -62,7 +80,7 @@ public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync(string ur
6280
/// <param name="url">The route of the API.</param>
6381
/// <param name="payload">The request body.</param>
6482
/// <typeparam name="TPayload">The type of request body.</typeparam>
65-
public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync<TPayload>(string url, TPayload payload)
83+
public async Task<CommandResponse<TError>> PostCommandAsync<TPayload>(string url, TPayload payload)
6684
{
6785
var response = await HttpClient.PostAsJsonAsync(url, payload);
6886
return await HandleCommandResponseAsync(response);
@@ -76,7 +94,7 @@ public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync<TPayload>
7694
/// <typeparam name="TResponse">The type of response body.</typeparam>
7795
/// <typeparam name="TPayload">The type of request body.</typeparam>
7896
/// <returns>The response body.</returns>
79-
public async Task<CommandResponse<TResponse, ServiceAgentError>> PostCommandAsync<TResponse, TPayload>(
97+
public async Task<CommandResponse<TResponse, TError>> PostCommandAsync<TResponse, TPayload>(
8098
string url,
8199
TPayload payload)
82100
{
@@ -88,7 +106,7 @@ public async Task<CommandResponse<TResponse, ServiceAgentError>> PostCommandAsyn
88106
/// Execute a command with PUT method and payload.
89107
/// </summary>
90108
/// <param name="url">The route of API.</param>
91-
public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync(string url)
109+
public async Task<CommandResponse<TError>> PutCommandAsync(string url)
92110
{
93111
var response = await HttpClient.PutAsync(url, new StringContent(string.Empty));
94112
return await HandleCommandResponseAsync(response);
@@ -101,7 +119,7 @@ public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync(string url
101119
/// <param name="payload">The request body.</param>
102120
/// <typeparam name="TPayload">The type of request body.</typeparam>
103121
/// <returns>The command response.</returns>
104-
public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync<TPayload>(string url, TPayload payload)
122+
public async Task<CommandResponse<TError>> PutCommandAsync<TPayload>(string url, TPayload payload)
105123
{
106124
var response = await HttpClient.PutAsJsonAsync(url, payload);
107125
return await HandleCommandResponseAsync(response);
@@ -115,7 +133,7 @@ public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync<TPayload>(
115133
/// <typeparam name="TResponse">The type of response body.</typeparam>
116134
/// <typeparam name="TPayload">The type of request body.</typeparam>
117135
/// <returns>The response body.</returns>
118-
public async Task<CommandResponse<TResponse, ServiceAgentError>> PutCommandAsync<TResponse, TPayload>(
136+
public async Task<CommandResponse<TResponse, TError>> PutCommandAsync<TResponse, TPayload>(
119137
string url,
120138
TPayload payload)
121139
{
@@ -237,54 +255,44 @@ public async Task<TList> ListItemsAsync<TList>(string url)
237255
return await HttpClient.GetFromJsonAsync<TList>(url) ?? new TList();
238256
}
239257

240-
private static async Task<CommandResponse<TResponse, ServiceAgentError>> HandleCommandResponseAsync<TResponse>(
258+
private static async Task<CommandResponse<TResponse, TError>> HandleCommandResponseAsync<TResponse>(
241259
HttpResponseMessage httpResponseMessage)
242260
{
243-
if (httpResponseMessage.IsSuccessStatusCode)
261+
if (httpResponseMessage.StatusCode == HttpStatusCode.NoContent)
262+
{
263+
return CommandResponse<TResponse, TError>.Success();
264+
}
265+
266+
if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
244267
{
245268
var result = await httpResponseMessage.Content.ReadFromJsonAsync<TResponse>();
246-
return CommandResponse<TResponse, ServiceAgentError>.Success(result);
269+
return CommandResponse<TResponse, TError>.Success(result);
247270
}
248271

249-
var response = await httpResponseMessage.Content.ReadFromJsonAsync<CommandResponse>();
272+
var response = await httpResponseMessage.Content.ReadFromJsonAsync<CommandResponse<TResponse, TError>>();
250273
if (response is null)
251274
{
252-
return CommandResponse<TResponse, ServiceAgentError>.Fail(ServiceAgentError.UnknownError);
275+
throw new InvalidOperationException(
276+
$"Could not deserialize error from response, response: {await httpResponseMessage.Content.ReadAsStringAsync()}");
253277
}
254278

255-
return new CommandResponse<TResponse, ServiceAgentError>
256-
{
257-
IsConcurrentError = response.IsConcurrentError,
258-
IsValidationError = response.IsValidationError,
259-
ErrorMessage = response.ErrorMessage,
260-
LockAcquired = response.LockAcquired,
261-
ValidationErrors = response.ValidationErrors,
262-
ErrorCode = new ServiceAgentError(1, response.ErrorMessage)
263-
};
279+
return response;
264280
}
265281

266-
private static async Task<CommandResponse<ServiceAgentError>> HandleCommandResponseAsync(
267-
HttpResponseMessage message)
282+
private static async Task<CommandResponse<TError>> HandleCommandResponseAsync(HttpResponseMessage message)
268283
{
269284
if (message.IsSuccessStatusCode)
270285
{
271-
return CommandResponse<ServiceAgentError>.Success();
286+
return CommandResponse<TError>.Success();
272287
}
273288

274-
var response = await message.Content.ReadFromJsonAsync<CommandResponse>();
289+
var response = await message.Content.ReadFromJsonAsync<CommandResponse<TError>>();
275290
if (response is null)
276291
{
277-
return CommandResponse<ServiceAgentError>.Fail(ServiceAgentError.UnknownError);
292+
throw new InvalidOperationException(
293+
$"Could not deserialize error from response, response: {await message.Content.ReadAsStringAsync()}");
278294
}
279295

280-
return new CommandResponse<ServiceAgentError>
281-
{
282-
IsConcurrentError = response.IsConcurrentError,
283-
IsValidationError = response.IsValidationError,
284-
ErrorMessage = response.ErrorMessage,
285-
LockAcquired = response.LockAcquired,
286-
ValidationErrors = response.ValidationErrors,
287-
ErrorCode = new ServiceAgentError(1, response.ErrorMessage)
288-
};
296+
return response;
289297
}
290298
}

test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@ public async Task MinimalApi_HavingError_CommandResponseAsync(bool needValidatio
8686
var response = await client.PutAsJsonAsync(
8787
"/api/v1/strings/1",
8888
new UpdatePayload(needExecutionError, needValidationError));
89-
var commandResponse = await response.Content.ReadFromJsonAsync<CommandResponse>();
89+
var commandResponse = await response.Content.ReadFromJsonAsync<CommandResponse<TestError>>();
9090

9191
// Assert
9292
response.Should().HaveClientError();
9393
commandResponse.Should().NotBeNull();
9494
commandResponse!.IsSuccess().Should().BeFalse();
95+
commandResponse.Should().BeEquivalentTo(new { IsValidationError = needValidationError });
96+
(commandResponse.ErrorCode != null).Should().Be(needExecutionError);
9597
}
9698

9799
[Theory]
@@ -178,13 +180,15 @@ public async Task Mvc_HavingError_CommandResponseAsync(bool needValidationError,
178180
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
179181
var response = await client.PutAsJsonAsync(
180182
"/api/v1/mvc/strings/1",
181-
new UpdatePayload(needValidationError, needExecutionError));
182-
var content = await response.Content.ReadFromJsonAsync<CommandResponse>();
183+
new UpdatePayload(needExecutionError, needValidationError));
184+
var content = await response.Content.ReadFromJsonAsync<CommandResponse<TestError>>();
183185

184186
// Assert
185187
response.Should().HaveClientError();
186188
content.Should().NotBeNull();
187189
content!.IsSuccess().Should().BeFalse();
190+
content.Should().BeEquivalentTo(new { IsValidationError = needValidationError });
191+
(content.ErrorCode != null).Should().Be(needExecutionError);
188192
}
189193

190194
[Theory]

0 commit comments

Comments
 (0)