命令模式和消息幂等去重
This commit is contained in:
parent
2201f36422
commit
780c489e02
14
src/HelloShop.OrderingService/Commands/IdentifiedCommand.cs
Normal file
14
src/HelloShop.OrderingService/Commands/IdentifiedCommand.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Commands
|
||||||
|
{
|
||||||
|
public class IdentifiedCommand<TRequest, TResponse>(TRequest command, Guid id) : IRequest<TResponse> where TRequest : IRequest<TResponse>
|
||||||
|
{
|
||||||
|
public TRequest Command { get; } = command;
|
||||||
|
|
||||||
|
public Guid Id { get; } = id;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
using HelloShop.OrderingService.Services;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Commands
|
||||||
|
{
|
||||||
|
public abstract class IdentifiedCommandHandler<TRequest, TResponse>(IMediator mediator, IClientRequestManager requestManager) : IRequestHandler<IdentifiedCommand<TRequest, TResponse>, TResponse> where TRequest : IRequest<TResponse>
|
||||||
|
{
|
||||||
|
protected virtual TResponse? CreateResultForDuplicateRequest() => default;
|
||||||
|
|
||||||
|
public async Task<TResponse> Handle(IdentifiedCommand<TRequest, TResponse> request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (await requestManager.ExistAsync(request.Id))
|
||||||
|
{
|
||||||
|
return CreateResultForDuplicateRequest() ?? throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestManager.CreateRequestForCommandAsync<TRequest>(request.Id);
|
||||||
|
|
||||||
|
return await mediator.Send(request.Command, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Commands.Orders
|
||||||
|
{
|
||||||
|
public class CreateOrderCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
public int UserId { get; set; }
|
||||||
|
|
||||||
|
public required string UserName { get; set; }
|
||||||
|
|
||||||
|
#region Order Items
|
||||||
|
|
||||||
|
public required IEnumerable<CreateOrderCommandItem> OrderItems { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region PaymentMethod
|
||||||
|
|
||||||
|
public required string CardAlias { get; set; }
|
||||||
|
|
||||||
|
public required string CardNumber { get; set; }
|
||||||
|
|
||||||
|
public required string CardHolderName { get; set; }
|
||||||
|
|
||||||
|
public string? CardSecurityNumber { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? CardExpiration { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Address
|
||||||
|
|
||||||
|
public required string Country { get; set; }
|
||||||
|
|
||||||
|
public required string State { get; set; }
|
||||||
|
|
||||||
|
public required string City { get; set; }
|
||||||
|
|
||||||
|
public required string Street { get; set; }
|
||||||
|
|
||||||
|
public required string ZipCode { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public class CreateOrderCommandItem
|
||||||
|
{
|
||||||
|
public int ProductId { get; set; }
|
||||||
|
|
||||||
|
public required string ProductName { get; set; }
|
||||||
|
|
||||||
|
public required string PictureUrl { get; set; }
|
||||||
|
|
||||||
|
public decimal UnitPrice { get; set; }
|
||||||
|
|
||||||
|
public int Units { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
using HelloShop.OrderingService.Services;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Commands.Orders
|
||||||
|
{
|
||||||
|
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, bool>
|
||||||
|
{
|
||||||
|
public Task<bool> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreateOrderIdentifiedCommandHandler(IMediator mediator, IClientRequestManager requestManager) : IdentifiedCommandHandler<CreateOrderCommand, bool>(mediator, requestManager)
|
||||||
|
{
|
||||||
|
protected override bool CreateResultForDuplicateRequest() => default;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Entities.Idempotency
|
||||||
|
{
|
||||||
|
public class ClientRequest
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset Time { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
using HelloShop.OrderingService.Constants;
|
using HelloShop.OrderingService.Constants;
|
||||||
using HelloShop.OrderingService.Infrastructure;
|
using HelloShop.OrderingService.Infrastructure;
|
||||||
|
using HelloShop.OrderingService.Services;
|
||||||
using HelloShop.ServiceDefaults.Extensions;
|
using HelloShop.ServiceDefaults.Extensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace HelloShop.OrderingService.Extensions
|
namespace HelloShop.OrderingService.Extensions
|
||||||
{
|
{
|
||||||
@ -18,6 +20,13 @@ namespace HelloShop.OrderingService.Extensions
|
|||||||
{
|
{
|
||||||
options.UseNpgsql(builder.Configuration.GetConnectionString(DbConstants.MasterConnectionStringName));
|
options.UseNpgsql(builder.Configuration.GetConnectionString(DbConstants.MasterConnectionStringName));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Services.AddScoped<IClientRequestManager, ClientRequestManager>();
|
||||||
|
|
||||||
|
builder.Services.AddMediatR(options =>
|
||||||
|
{
|
||||||
|
options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebApplication MapApplicationEndpoints(this WebApplication app)
|
public static WebApplication MapApplicationEndpoints(this WebApplication app)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
|
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MediatR" Version="12.4.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
using HelloShop.OrderingService.Entities.Idempotency;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Infrastructure.EntityConfigurations.Idempotency
|
||||||
|
{
|
||||||
|
public class ClientRequestEntityTypeConfiguration : IEntityTypeConfiguration<ClientRequest>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ClientRequest> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("ClientRequests");
|
||||||
|
|
||||||
|
builder.HasKey(t => t.Id);
|
||||||
|
|
||||||
|
builder.Property(t => t.Name).HasMaxLength(64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
using HelloShop.OrderingService.Entities.Idempotency;
|
||||||
|
using HelloShop.OrderingService.Infrastructure;
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Services
|
||||||
|
{
|
||||||
|
public class ClientRequestManager(OrderingServiceDbContext dbContext) : IClientRequestManager
|
||||||
|
{
|
||||||
|
public async Task<bool> ExistAsync(Guid id) => await dbContext.FindAsync<ClientRequest>(id) is not null;
|
||||||
|
|
||||||
|
public async Task CreateRequestForCommandAsync<T>(Guid id)
|
||||||
|
{
|
||||||
|
if (await ExistAsync(id))
|
||||||
|
{
|
||||||
|
throw new Exception($"Request with {id} already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbContext.AddAsync(new ClientRequest
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = typeof(T).Name,
|
||||||
|
Time = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||||
|
// See the license file in the project root for more information.
|
||||||
|
|
||||||
|
namespace HelloShop.OrderingService.Services
|
||||||
|
{
|
||||||
|
public interface IClientRequestManager
|
||||||
|
{
|
||||||
|
Task<bool> ExistAsync(Guid id);
|
||||||
|
|
||||||
|
Task CreateRequestForCommandAsync<T>(Guid id);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user