实现 CQRS 中的 Query 模式

This commit is contained in:
hello 2024-11-20 08:04:27 +08:00
parent aeefff6c0b
commit 53bf517224
9 changed files with 164 additions and 2 deletions

View File

@ -5,6 +5,7 @@ using AutoMapper;
using HelloShop.OrderingService.Commands.Orders;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Models.Orders;
using HelloShop.OrderingService.Queries;
namespace HelloShop.OrderingService.AutoMapper
{
@ -16,6 +17,18 @@ namespace HelloShop.OrderingService.AutoMapper
CreateMap<BasketItem, CreateOrderCommand.CreateOrderCommandItem>().ForMember(dest => dest.Units, opt => opt.MapFrom(src => src.Quantity));
CreateMap<CreateOrderCommand.CreateOrderCommandItem, OrderItem>();
CreateMap<CreateOrderCommand, Address>();
CreateMap<Order, OrderSummary>().ForMember(dest => dest.OrderId, opt => opt.MapFrom(src => src.Id)).ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.OrderStatus));
CreateMap<Order, OrderDetails>()
.ForMember(dest => dest.OrderId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.OrderStatus))
.ForMember(dest => dest.Country, opt => opt.MapFrom(src => src.Address.Country))
.ForMember(dest => dest.State, opt => opt.MapFrom(src => src.Address.State))
.ForMember(dest => dest.City, opt => opt.MapFrom(src => src.Address.City))
.ForMember(dest => dest.Street, opt => opt.MapFrom(src => src.Address.Street))
.ForMember(dest => dest.ZipCode, opt => opt.MapFrom(src => src.Address.ZipCode));
CreateMap<OrderItem, OrderDetailsItem>();
}
}
}

View File

@ -5,6 +5,7 @@ using AutoMapper;
using HelloShop.OrderingService.Commands;
using HelloShop.OrderingService.Commands.Orders;
using HelloShop.OrderingService.Models.Orders;
using HelloShop.OrderingService.Queries;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -62,7 +63,6 @@ public class OrdersController(ILogger<OrdersController> logger, IMediator mediat
}
}
[HttpPut("Cancel/{id}")]
public async Task<IActionResult> CancelOrder([FromHeader(Name = "x-request-id")] Guid requestId, int id)
{
@ -96,4 +96,27 @@ public class OrdersController(ILogger<OrdersController> logger, IMediator mediat
return Ok();
}
[HttpGet]
public async Task<IActionResult> GetOrders([FromServices] IOrderQueries orderQueries)
{
string? nameIdentifier = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (!int.TryParse(nameIdentifier, out int userId))
{
throw new ApplicationException("User id not found in claims.");
}
IEnumerable<OrderSummary> orders = await orderQueries.GetOrdersFromUserAsync(userId);
return Ok(orders);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(int id, [FromServices] IOrderQueries orderQueries)
{
OrderDetails order = await orderQueries.GetOrderAsync(id);
return Ok(order);
}
}

View File

@ -4,6 +4,7 @@
using HelloShop.OrderingService.Behaviors;
using HelloShop.OrderingService.Constants;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.OrderingService.Queries;
using HelloShop.OrderingService.Services;
using HelloShop.OrderingService.Workers;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
@ -48,6 +49,8 @@ namespace HelloShop.OrderingService.Extensions
options.AddOpenBehavior(typeof(TransactionBehavior<,>));
});
builder.Services.AddScoped<IOrderQueries, OrderQueries>();
builder.Services.AddModelMapper().AddModelValidator();
builder.Services.AddTransient<ISmsSender, MessageService>().AddTransient<IEmailSender, MessageService>();

View File

@ -34,4 +34,14 @@ Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwi
]
}
###
###
GET {{HelloShop.OrderingService_HostAddress}}/api/orders
Content-Type: application/json
Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3MjA1NzY3NDYsImV4cCI6MTc0MjYwODc0NiwiaWF0IjoxNzIwNTc2NzQ2fQ.ju_D3zeGLKqJYVckbb8Y3yNkp40nOqRAJrdOsISs4d4
###
GET {{HelloShop.OrderingService_HostAddress}}/api/orders/1
Content-Type: application/json
Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3MjA1NzY3NDYsImV4cCI6MTc0MjYwODc0NiwiaWF0IjoxNzIwNTc2NzQ2fQ.ju_D3zeGLKqJYVckbb8Y3yNkp40nOqRAJrdOsISs4d4

View File

@ -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.Queries
{
public interface IOrderQueries
{
Task<OrderDetails> GetOrderAsync(int id);
Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(int userId);
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.OrderingService.Queries
{
public class OrderDetails
{
public int OrderId { get; init; }
public required string Status { get; init; }
public DateTimeOffset OrderDate { get; init; }
public required string Country { get; init; }
public required string State { get; init; }
public required string City { get; init; }
public required string Street { get; init; }
public required string ZipCode { get; init; }
public string? Description { get; init; }
public required List<OrderDetailsItem> OrderItems { get; set; }
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.OrderingService.Queries
{
public class OrderDetailsItem
{
public int ProductId { get; init; }
public required string ProductName { get; init; }
public int Units { get; init; }
public double UnitPrice { get; init; }
public required string PictureUrl { get; init; }
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using AutoMapper;
using HelloShop.OrderingService.Constants;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.Queries
{
public class OrderQueries : IOrderQueries
{
private readonly OrderingServiceDbContext _dbContext;
private readonly IMapper _mapper;
public OrderQueries(OrderingServiceDbContext dbContext, IMapper mapper, IConfiguration configuration)
{
_dbContext = dbContext;
// TODOProvide the AddKeyedDbContext extension method on Services at https://github.com/dotnet/efcore/issues/34591
dbContext.Database.SetConnectionString(configuration.GetConnectionString(DbConstants.SlaveConnectionStringName));
_mapper = mapper;
}
public async Task<OrderDetails> GetOrderAsync(int id)
{
var order = await _dbContext.Set<Order>().AsNoTracking().Include(o => o.OrderItems).SingleOrDefaultAsync(o => o.Id == id);
var orderDetails = _mapper.Map<OrderDetails>(order);
return orderDetails;
}
public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(int userId)
{
var orders = await _dbContext.Set<Order>().AsNoTracking().Where(o => o.BuyerId == userId).ToListAsync();
var orderSummaries = _mapper.Map<IEnumerable<OrderSummary>>(orders);
return orderSummaries;
}
}
}

View File

@ -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.Queries
{
public class OrderSummary
{
public int OrderId { get; init; }
public required string Status { get; init; }
public DateTimeOffset OrderDate { get; init; }
}
}