Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ protected virtual IActionResult CustomErrorCommandResponseMap<TError>(CommandRes
private IActionResult MapErrorCommandResponseToCqrsResponse<TError>(CommandResponse<TError> response)
where TError : Enumeration
{
if (response is { IsConcurrentError: true, LockAcquired: false })
{
return StatusCode(429);
}

return BadRequest(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ private IResult HandleErrorCommandResponse(CommandResponse response, HttpContext

private static IResult HandleErrorCommandResponseWithCqrs(CommandResponse response)
{
return Results.BadRequest(response);
if (response is { IsConcurrentError: true, LockAcquired: false })
{
return Results.StatusCode(429);
}

return Results.BadRequest((object)response);
}

private static IResult HandleErrorCommandResponseWithPlainText(CommandResponse response)
Expand Down
82 changes: 45 additions & 37 deletions src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
using System.Net;
using System.Net.Http.Json;
using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;

namespace Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent;

/// <summary>
/// Base Class for CQRS Service Agent.
/// </summary>
public abstract class CqrsServiceAgent : CqrsServiceAgent<ServiceAgentError>
{
/// <summary>
/// Create a Cqrs service agent.
/// </summary>
/// <param name="httpClient">The underlying http client.</param>
protected CqrsServiceAgent(HttpClient httpClient)
: base(httpClient)
{
}
}

/// <summary>
/// Service Agent for CQRS
/// </summary>
public abstract class CqrsServiceAgent
/// <typeparam name="TError">The type of error for this service.</typeparam>
public abstract class CqrsServiceAgent<TError>
where TError : Enumeration
{
/// <summary>
/// The underlying <see cref="HttpClient"/>.
Expand All @@ -30,7 +48,7 @@ protected CqrsServiceAgent(HttpClient httpClient)
/// <param name="url">The url.</param>
/// <typeparam name="TResponse">Response type.</typeparam>
/// <returns>The response.</returns>
public async Task<CommandResponse<TResponse, ServiceAgentError>> DeleteCommandAsync<TResponse>(string url)
public async Task<CommandResponse<TResponse, TError>> DeleteCommandAsync<TResponse>(string url)
{
var response = await HttpClient.DeleteAsync(url);
return await HandleCommandResponseAsync<TResponse>(response);
Expand All @@ -40,7 +58,7 @@ public async Task<CommandResponse<TResponse, ServiceAgentError>> DeleteCommandAs
/// Execute a command with DELETE method.
/// </summary>
/// <param name="url">The route of the API.</param>
public async Task<CommandResponse<ServiceAgentError>> DeleteCommandAsync(string url)
public async Task<CommandResponse<TError>> DeleteCommandAsync(string url)
{
var response = await HttpClient.DeleteAsync(url);
return await HandleCommandResponseAsync(response);
Expand All @@ -50,7 +68,7 @@ public async Task<CommandResponse<ServiceAgentError>> DeleteCommandAsync(string
/// Execute a command with POST method.
/// </summary>
/// <param name="url">The route of the API.</param>
public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync(string url)
public async Task<CommandResponse<TError>> PostCommandAsync(string url)
{
var response = await HttpClient.PostAsync(url, new StringContent(string.Empty));
return await HandleCommandResponseAsync(response);
Expand All @@ -62,7 +80,7 @@ public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync(string ur
/// <param name="url">The route of the API.</param>
/// <param name="payload">The request body.</param>
/// <typeparam name="TPayload">The type of request body.</typeparam>
public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync<TPayload>(string url, TPayload payload)
public async Task<CommandResponse<TError>> PostCommandAsync<TPayload>(string url, TPayload payload)
{
var response = await HttpClient.PostAsJsonAsync(url, payload);
return await HandleCommandResponseAsync(response);
Expand All @@ -76,7 +94,7 @@ public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync<TPayload>
/// <typeparam name="TResponse">The type of response body.</typeparam>
/// <typeparam name="TPayload">The type of request body.</typeparam>
/// <returns>The response body.</returns>
public async Task<CommandResponse<TResponse, ServiceAgentError>> PostCommandAsync<TResponse, TPayload>(
public async Task<CommandResponse<TResponse, TError>> PostCommandAsync<TResponse, TPayload>(
string url,
TPayload payload)
{
Expand All @@ -88,7 +106,7 @@ public async Task<CommandResponse<TResponse, ServiceAgentError>> PostCommandAsyn
/// Execute a command with PUT method and payload.
/// </summary>
/// <param name="url">The route of API.</param>
public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync(string url)
public async Task<CommandResponse<TError>> PutCommandAsync(string url)
{
var response = await HttpClient.PutAsync(url, new StringContent(string.Empty));
return await HandleCommandResponseAsync(response);
Expand All @@ -101,7 +119,7 @@ public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync(string url
/// <param name="payload">The request body.</param>
/// <typeparam name="TPayload">The type of request body.</typeparam>
/// <returns>The command response.</returns>
public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync<TPayload>(string url, TPayload payload)
public async Task<CommandResponse<TError>> PutCommandAsync<TPayload>(string url, TPayload payload)
{
var response = await HttpClient.PutAsJsonAsync(url, payload);
return await HandleCommandResponseAsync(response);
Expand All @@ -115,7 +133,7 @@ public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync<TPayload>(
/// <typeparam name="TResponse">The type of response body.</typeparam>
/// <typeparam name="TPayload">The type of request body.</typeparam>
/// <returns>The response body.</returns>
public async Task<CommandResponse<TResponse, ServiceAgentError>> PutCommandAsync<TResponse, TPayload>(
public async Task<CommandResponse<TResponse, TError>> PutCommandAsync<TResponse, TPayload>(
string url,
TPayload payload)
{
Expand Down Expand Up @@ -237,54 +255,44 @@ public async Task<TList> ListItemsAsync<TList>(string url)
return await HttpClient.GetFromJsonAsync<TList>(url) ?? new TList();
}

private static async Task<CommandResponse<TResponse, ServiceAgentError>> HandleCommandResponseAsync<TResponse>(
private static async Task<CommandResponse<TResponse, TError>> HandleCommandResponseAsync<TResponse>(
HttpResponseMessage httpResponseMessage)
{
if (httpResponseMessage.IsSuccessStatusCode)
if (httpResponseMessage.StatusCode == HttpStatusCode.NoContent)
{
return CommandResponse<TResponse, TError>.Success();
}

if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
{
var result = await httpResponseMessage.Content.ReadFromJsonAsync<TResponse>();
return CommandResponse<TResponse, ServiceAgentError>.Success(result);
return CommandResponse<TResponse, TError>.Success(result);
}

var response = await httpResponseMessage.Content.ReadFromJsonAsync<CommandResponse>();
var response = await httpResponseMessage.Content.ReadFromJsonAsync<CommandResponse<TResponse, TError>>();
if (response is null)
{
return CommandResponse<TResponse, ServiceAgentError>.Fail(ServiceAgentError.UnknownError);
throw new InvalidOperationException(
$"Could not deserialize error from response, response: {await httpResponseMessage.Content.ReadAsStringAsync()}");
}

return new CommandResponse<TResponse, ServiceAgentError>
{
IsConcurrentError = response.IsConcurrentError,
IsValidationError = response.IsValidationError,
ErrorMessage = response.ErrorMessage,
LockAcquired = response.LockAcquired,
ValidationErrors = response.ValidationErrors,
ErrorCode = new ServiceAgentError(1, response.ErrorMessage)
};
return response;
}

private static async Task<CommandResponse<ServiceAgentError>> HandleCommandResponseAsync(
HttpResponseMessage message)
private static async Task<CommandResponse<TError>> HandleCommandResponseAsync(HttpResponseMessage message)
{
if (message.IsSuccessStatusCode)
{
return CommandResponse<ServiceAgentError>.Success();
return CommandResponse<TError>.Success();
}

var response = await message.Content.ReadFromJsonAsync<CommandResponse>();
var response = await message.Content.ReadFromJsonAsync<CommandResponse<TError>>();
if (response is null)
{
return CommandResponse<ServiceAgentError>.Fail(ServiceAgentError.UnknownError);
throw new InvalidOperationException(
$"Could not deserialize error from response, response: {await message.Content.ReadAsStringAsync()}");
}

return new CommandResponse<ServiceAgentError>
{
IsConcurrentError = response.IsConcurrentError,
IsValidationError = response.IsValidationError,
ErrorMessage = response.ErrorMessage,
LockAcquired = response.LockAcquired,
ValidationErrors = response.ValidationErrors,
ErrorCode = new ServiceAgentError(1, response.ErrorMessage)
};
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ public async Task MinimalApi_HavingError_CommandResponseAsync(bool needValidatio
var response = await client.PutAsJsonAsync(
"/api/v1/strings/1",
new UpdatePayload(needExecutionError, needValidationError));
var commandResponse = await response.Content.ReadFromJsonAsync<CommandResponse>();
var commandResponse = await response.Content.ReadFromJsonAsync<CommandResponse<TestError>>();

// Assert
response.Should().HaveClientError();
commandResponse.Should().NotBeNull();
commandResponse!.IsSuccess().Should().BeFalse();
commandResponse.Should().BeEquivalentTo(new { IsValidationError = needValidationError });
(commandResponse.ErrorCode != null).Should().Be(needExecutionError);
}

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

// Assert
response.Should().HaveClientError();
content.Should().NotBeNull();
content!.IsSuccess().Should().BeFalse();
content.Should().BeEquivalentTo(new { IsValidationError = needValidationError });
(content.ErrorCode != null).Should().Be(needExecutionError);
}

[Theory]
Expand Down