命令模式和消息幂等去重
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.Infrastructure;
|
||||
using HelloShop.OrderingService.Services;
|
||||
using HelloShop.ServiceDefaults.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HelloShop.OrderingService.Extensions
|
||||
{
|
||||
@ -18,6 +20,13 @@ namespace HelloShop.OrderingService.Extensions
|
||||
{
|
||||
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)
|
||||
|
@ -8,6 +8,7 @@
|
||||
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
||||
<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