gpt4 book ai didi

asp.net-mvc - 从命令运行查询是否违反了命令-查询分隔?

转载 作者:行者123 更新时间:2023-12-04 15:32:18 25 4
gpt4 key购买 nike

给定现实世界中的匿名购物车,“AddToCart”工作流程必须执行以下步骤:

  • 从数据库中查找当前产品。从产品获取价格或使用服务来计算用户选择和其他产品属性的价格。 (查询)
  • 从数据库中查找当前的购物车。 (查询)
  • 如果数据库中不存在当前购物车,请在内存中创建一个新的购物车实体。
  • 将新商品(产品)及其价格添加到购物车实体(在内存中)。
  • 在整个购物车上运行所有折扣计算。 (取决于查询)
  • 在购物车上运行所有营业税计算。 (取决于查询)
  • 在购物车上运行所有运费计算。 (取决于查询)
  • 如果这是新的购物车,则将实体添加到数据库,否则更新数据库中的购物车。 (命令)

  • 因此,尽管“AddToCart”听起来像应该是一个命令(因为它会更新系统状态),但实际上它取决于许多查询。

    我的问题

    处理这种工作流程的普遍接受的方法是什么?
  • 生成AddToCartCommandHandler,它取决于可能运行查询的其他服务。
  • 制作外观CartService,以协调首先运行查询和命令的工作流程。
  • 使 Controller 操作方法首先运行查询,然后运行任何命令。如果需要重新使用某些查询步骤,可能会错过一些查询步骤。
  • 其他吗?

  • 是我找不到答案的原因,因为它“取决于设计”,这是不应用它的异常(exception)之一?

    如果命令和查询是分开的,我可以将我的真实 Entity Framework 实体类传递给添加/更新购物车的命令(以便EF可以算出是否已附加购物车)吗?在这种情况下,DTO似乎无法使用。

    NOTE: I am implicitly assuming that systems that implement CQS do so with the aim that eventually they could become a full-on CQRS system. If so, this workflow apparently would not be able to make the transition - hence my question.



    背景

    我要去CQS的第一场刺。

    从我已阅读的有关该模式的文档中可以清楚地看出 ,查询不能更改系统状态

    但是, 尚不清楚是否可以从命令中运行查询是否可行(我似乎在任何地方都找不到任何信息)。

    我可以想到几个实际案例,这些案例需要在哪里发生。但是,鉴于在线上缺乏这种模式的真实示例,我不确定如何进行。在线上有很多理论,但是我能找到的唯一代码是 herehere

    最佳答案

    这个问题的答案来自qujck的a comment形式。

    解决方案是将应用程序分为不同的查询类型和命令类型。每种类型的确切目的仍然是个谜(因为博客文章没有介绍他做出这种区分的原因),但是它确实清楚了高层和中层命令如何依赖于数据库查询。

    命令类型

  • 命令(顶层)
  • 命令策略(中级)
  • 数据命令(直接数据访问)

  • 查询类型
  • 查询(顶级)
  • 查询策略(中级)
  • 数据查询(直接数据访问)

  • 命令查询实现
    // Commands

    public interface ICommand
    {
    }

    public interface IDataCommand
    {
    }

    /// <summary>
    /// A holistic abstraction, an abstraction that acts as the whole of each transaction
    /// </summary>
    /// <typeparam name="TCommand"></typeparam>
    public interface ICommandHandler<TCommand>
    {
    void Handle(TCommand command);
    }

    public interface ICommandStrategyHandler<TCommand> where TCommand : ICommand
    {
    void Handle(TCommand command);
    }

    /// <summary>
    /// Direct database update
    /// </summary>
    /// <typeparam name="TCommand"></typeparam>
    public interface IDataCommandHandler<TCommand> where TCommand : IDataCommand
    {
    void Handle(TCommand command);
    }



    // Queries

    public interface IQuery<TResult>
    {
    }

    public interface IDataQuery<TResult>
    {
    }

    /// <summary>
    /// A holistic abstraction, an abstraction that acts as the whole of each transaction
    /// </summary>
    /// <typeparam name="TQuery"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
    {
    TResult Handle(TQuery query);
    }

    public interface IQueryStrategyHandler<TQuery, TResult> where TQuery : IQuery<TResult>
    {
    TResult Handle(TQuery query);
    }

    /// <summary>
    /// Direct database query
    /// </summary>
    /// <typeparam name="TQuery"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    public interface IDataQueryHandler<TQuery, TResult> where TQuery : IDataQuery<TResult>
    {
    TResult Handle(TQuery query);
    }


    /// <summary>
    /// Generic processor that can run any query
    /// </summary>
    public interface IQueryProcessor
    {
    TResult Execute<TResult>(IQuery<TResult> query);

    // NOTE: Stephen recommends against using Async. He may be right that it is not
    // worth the aggrevation of bugs that may be introduced.
    //Task<TResult> Execute<TResult>(IQuery<TResult> query);

    TResult Execute<TResult>(IDataQuery<TResult> query);
    }

    AddToCart依赖图

    使用以上实现,AddToCart工作流依赖关系图的结构如下所示。
  • AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
  • GetShoppingCartDetailsQueryHandler : IQueryHandler<GetShoppingCartDetailsQuery, ShoppingCartDetails>
  • GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
  • GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
  • ApplicationDbContext
  • CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
  • ApplicationDbContext
  • UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
  • SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
  • GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
  • ApplicationDbContext
  • SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
  • SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
  • ?
  • SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>

  • 执行

    DTO
    public class ShoppingCartDetails : IOrder
    {
    private IEnumerable<IOrderItem> items = new List<ShoppingCartItem>();

    public Guid Id { get; set; }
    public decimal SubtotalDiscounts { get; set; }
    public string ShippingPostalCode { get; set; }
    public decimal Shipping { get; set; }
    public decimal ShippingDiscounts { get; set; }
    public decimal SalesTax { get; set; }
    public decimal SalesTaxDiscounts { get; set; }

    // Declared twice - once for the IOrder interface
    // and once so we can get the realized concrete type.
    // See: https://stackoverflow.com/questions/15490633/why-cant-i-use-a-compatible-concrete-type-when-implementing-an-interface
    public IEnumerable<ShoppingCartItem> Items
    {
    get { return this.items as IEnumerable<ShoppingCartItem>; }
    set { this.items = value; }
    }
    IEnumerable<IOrderItem> IOrder.Items
    {
    get { return this.items; }
    set { this.items = value; }
    }

    //public IEnumerable<ShoppingCartNotification> Notifications { get; set; }
    //public IEnumerable<ShoppingCartCoupon> Coupons { get; set; } // TODO: Add this to IOrder
    }

    public class ShoppingCartItem : IOrderItem
    {
    public ShoppingCartItem()
    {
    this.Id = Guid.NewGuid();
    this.Selections = new Dictionary<string, object>();
    }

    public Guid Id { get; set; }
    public Guid ShoppingCartId { get; set; }
    public Guid ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
    public decimal PriceDiscount { get; set; }
    public IDictionary<string, object> Selections { get; set; }
    }

    public class ProductDetails
    {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public decimal Discount { get; set; }
    }

    计算订单总数

    我不是依靠一连串的服务来执行简单的(并且是必需的)算术,而是选择将此行为放入扩展方法中,以便根据实际数据即时完成此行为。由于将需要在购物车,订单和报价之间共享此逻辑,因此将根据 IOrderIOrderItem而不是具体的模型类型来进行计算。
    // Contract to share simple cacluation and other business logic between shopping cart, order, and quote
    public interface IOrder
    {
    decimal SubtotalDiscounts { get; set; }
    decimal Shipping { get; set; }
    decimal ShippingDiscounts { get; set; }
    decimal SalesTax { get; set; }
    decimal SalesTaxDiscounts { get; set; }
    IEnumerable<IOrderItem> Items { get; set; }
    }

    public interface IOrderItem
    {
    Guid ProductId { get; set; }
    int Quantity { get; set; }
    decimal Price { get; set; }
    decimal PriceDiscount { get; set; }
    IDictionary<string, object> Selections { get; set; }
    }

    public static class OrderExtensions
    {
    public static decimal GetSubtotal(this IOrder order)
    {
    return order.Items.Sum(x => x.GetTotal());
    }

    public static decimal GetSubtotalBeforeDiscounts(this IOrder order)
    {
    return order.Items.Sum(x => x.GetTotalBeforeDiscounts());
    }

    public static decimal GetTotal(this IOrder order)
    {
    var subtotal = (order.GetSubtotal() - order.SubtotalDiscounts);
    var shipping = (order.Shipping - order.ShippingDiscounts);
    var salesTax = (order.SalesTax - order.SalesTaxDiscounts);
    return (subtotal + shipping + salesTax);
    }
    }

    public static class OrderItemExtensions
    {
    public static decimal GetTotalBeforeDiscounts(this IOrderItem item)
    {
    return (item.Price * item.Quantity);
    }

    public static decimal GetTotal(this IOrderItem item)
    {
    return (GetTotalBeforeDiscounts(item) - item.PriceDiscount);
    }

    public static decimal GetDiscountedUnitPrice(this IOrderItem item)
    {
    return (item.Quantity > 0) ? (GetTotal(item) / item.Quantity) : 0;
    }
    }

    ShoppingCartController

    为简便起见,我们仅显示AddToCart Action ,但这也是针对购物车的其他 Action (即从购物车中删除)也将进行的地方。
    public class ShoppingCartController : Controller
    {
    private readonly IQueryProcessor queryProcessor;
    private readonly IAnonymousIdAccessor anonymousIdAccessor;
    private readonly ICommandHandler<AddToCartCommand> addToCartHandler;

    public ShoppingCartController(
    IQueryProcessor queryProcessor,
    IAnonymousIdAccessor anonymousIdAccessor,
    ICommandHandler<AddToCartCommand> addToCartHandler)
    {
    if (queryProcessor == null)
    throw new ArgumentNullException("queryProcessor");
    if (anonymousIdAccessor == null)
    throw new ArgumentNullException("anonymousIdAccessor");
    if (addToCartHandler == null)
    throw new ArgumentNullException("addToCartHandler");

    this.queryProcessor = queryProcessor;
    this.anonymousIdAccessor = anonymousIdAccessor;
    this.addToCartHandler = addToCartHandler;
    }

    public ActionResult Index()
    {
    var command = new GetShoppingCartDetailsQuery
    {
    ShoppingCartId = this.anonymousIdAccessor.AnonymousID
    };

    ShoppingCartDetails cart = this.queryProcessor.Execute(command);

    return View(cart);
    }

    public ActionResult AddToCart(ItemViewModel model)
    {
    var command = new AddToCartCommand
    {
    ProductId = model.Id,
    Quantity = model.Qty,
    Selections = model.Selections,
    ShoppingCartId = this.anonymousIdAccessor.AnonymousID
    };

    this.addToCartHandler.Handle(command);

    // If we execute server side, it should go to the cart page
    return RedirectToAction("Index");
    }
    }

    AddToCartCommandHandler

    这是工作流主要部分的执行位置。该命令将直接从 AddToCart Controller 操作中调用。
    public class AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
    {
    private readonly IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartQuery;
    private readonly IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand;
    private readonly ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand;
    private readonly ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand;

    public AddToCartCommandHandler(
    IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartCommand,
    IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand,
    ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand,
    ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand
    )
    {
    if (getShoppingCartCommand == null)
    throw new ArgumentNullException("getShoppingCartCommand");
    if (setItemPriceCommand == null)
    throw new ArgumentNullException("setItemPriceCommand");
    if (updateShoppingCartCommand == null)
    throw new ArgumentNullException("updateShoppingCartCommand");
    if (setTotalsCommand == null)
    throw new ArgumentNullException("setTotalsCommand");

    this.getShoppingCartQuery = getShoppingCartCommand;
    this.updateShoppingCartCommand = updateShoppingCartCommand;
    this.setItemPriceCommand = setItemPriceCommand;
    this.setTotalsCommand = setTotalsCommand;
    }

    public void Handle(AddToCartCommand command)
    {
    // Get the shopping cart (aggregate root) from the database
    var shoppingCart = getShoppingCartQuery.Handle(new GetShoppingCartQueryStrategy { ShoppingCartId = command.ShoppingCartId });

    // Create a new shopping cart item
    var item = new Contract.DTOs.ShoppingCartItem
    {
    ShoppingCartId = command.ShoppingCartId,
    ProductId = command.ProductId,
    Quantity = command.Quantity,

    // Dictionary representing the option selections the user made on the UI
    Selections = command.Selections
    };

    // Set the item's price (calculated/retrieved from database query)
    setItemPriceCommand.Handle(new SetItemPriceCommandStrategy { ShoppingCartItem = item });

    // Add the item to the cart
    var items = new List<Contract.DTOs.ShoppingCartItem>(shoppingCart.Items);
    items.Add(item);
    shoppingCart.Items = items;

    // Set the shopping cart totals (sales tax, discounts)
    setTotalsCommand.Handle(new SetTotalsCommandStrategy { ShoppingCart = shoppingCart });

    // Update the shopping cart details in the database
    updateShoppingCartCommand.Handle(new UpdateShoppingCartDataCommand { ShoppingCart = shoppingCart });
    }
    }

    GetShoppingCartQueryStrategyHandler
    public class GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
    {
    private readonly IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery;
    private readonly IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand;

    public GetShoppingCartQueryStrategyHandler(
    IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery,
    IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand)
    {
    if (getShoppingCartDataQuery == null)
    throw new ArgumentNullException("getShoppingCartDataQuery");
    if (createShoppingCartDataCommand == null)
    throw new ArgumentNullException("createShoppingCartDataCommand");

    this.getShoppingCartDataQuery = getShoppingCartDataQuery;
    this.createShoppingCartDataCommand = createShoppingCartDataCommand;
    }

    public ShoppingCartDetails Handle(GetShoppingCartQueryStrategy query)
    {
    var result = this.getShoppingCartDataQuery.Handle(new GetShoppingCartDataQuery { ShoppingCartId = query.ShoppingCartId });

    // If there is no shopping cart, create one.
    if (result == null)
    {
    this.createShoppingCartDataCommand.Handle(new CreateShoppingCartDataCommand { ShoppingCartId = query.ShoppingCartId });

    result = new ShoppingCartDetails
    {
    Id = query.ShoppingCartId
    };
    }

    return result;
    }
    }

    GetShoppingCartDataQueryHandler
    /// <summary>
    /// Data handler to get the shopping cart data (if it exists)
    /// </summary>
    public class GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
    {
    private readonly IAppContext context;

    public GetShoppingCartDataQueryHandler(IAppContext context)
    {
    if (context == null)
    throw new ArgumentNullException("context");
    this.context = context;
    }

    public ShoppingCartDetails Handle(GetShoppingCartDataQuery query)
    {
    return (from shoppingCart in context.ShoppingCarts
    where shoppingCart.Id == query.ShoppingCartId
    select new ShoppingCartDetails
    {
    Id = shoppingCart.Id,
    SubtotalDiscounts = shoppingCart.SubtotalDiscounts,
    ShippingPostalCode = shoppingCart.ShippingPostalCode,
    Shipping = shoppingCart.Shipping,
    ShippingDiscounts = shoppingCart.ShippingDiscounts,
    SalesTax = shoppingCart.SalesTax,
    SalesTaxDiscounts = shoppingCart.SalesTaxDiscounts,

    Items = shoppingCart.Items.Select(i =>
    new Contract.DTOs.ShoppingCartItem
    {
    Id = i.Id,
    ShoppingCartId = i.ShoppingCartId,
    ProductId = i.ProductId,
    Quantity = i.Quantity,
    Price = i.Price,
    PriceDiscount = i.PriceDiscount
    // TODO: Selections...
    })
    }).FirstOrDefault();
    }
    }

    CreateShoppingCartDataCommandHandler
    public class CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
    {
    private readonly IAppContext context;

    public CreateShoppingCartDataCommandHandler(IAppContext context)
    {
    if (context == null)
    throw new ArgumentNullException("context");
    this.context = context;
    }

    public void Handle(CreateShoppingCartDataCommand command)
    {
    var cart = new ShoppingCart
    {
    Id = command.ShoppingCartId
    };

    this.context.ShoppingCarts.Add(cart);
    this.context.SaveChanges();
    }
    }

    UpdateShoppingCartDataCommandHandler

    这将使用业务层应用的所有更改来更新购物车。

    目前,此“命令”会执行查询,以便协调数据库和内存副本之间的差异。但是,这显然违反了CQS模式。我计划提出一个后续问题,以确定变更跟踪的最佳措施是什么,因为变更跟踪和CQS似乎密切相关。
    public class UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
    {
    private readonly IAppContext context;

    public UpdateShoppingCartDataCommandHandler(IAppContext context)
    {
    if (context == null)
    throw new ArgumentNullException("context");
    this.context = context;
    }

    public void Handle(UpdateShoppingCartDataCommand command)
    {
    var cart = context.ShoppingCarts
    .Include(x => x.Items)
    .Single(x => x.Id == command.ShoppingCart.Id);


    cart.Id = command.ShoppingCart.Id;
    cart.SubtotalDiscounts = command.ShoppingCart.SubtotalDiscounts;
    cart.ShippingPostalCode = command.ShoppingCart.ShippingPostalCode;
    cart.Shipping = command.ShoppingCart.Shipping;
    cart.ShippingDiscounts = command.ShoppingCart.ShippingDiscounts;
    cart.SalesTax = command.ShoppingCart.SalesTax;
    cart.SalesTaxDiscounts = command.ShoppingCart.SalesTaxDiscounts;

    ReconcileShoppingCartItems(cart.Items, command.ShoppingCart.Items, command.ShoppingCart.Id);

    // Update the cart with new data

    context.SaveChanges();
    }

    private void ReconcileShoppingCartItems(ICollection<ShoppingCartItem> items, IEnumerable<Contract.DTOs.ShoppingCartItem> itemDtos, Guid shoppingCartId)
    {
    // remove deleted items
    var items2 = new List<ShoppingCartItem>(items);
    foreach (var item in items2)
    {
    if (!itemDtos.Any(x => x.Id == item.Id))
    {
    context.Entry(item).State = EntityState.Deleted;
    }
    }


    // Add/update items
    foreach (var dto in itemDtos)
    {
    var item = items.FirstOrDefault(x => x.Id == dto.Id);
    if (item == null)
    {
    items.Add(new ShoppingCartItem
    {
    Id = Guid.NewGuid(),
    ShoppingCartId = shoppingCartId,
    ProductId = dto.ProductId,
    Quantity = dto.Quantity,
    Price = dto.Price,
    PriceDiscount = dto.PriceDiscount
    });
    }
    else
    {
    item.ProductId = dto.ProductId;
    item.Quantity = dto.Quantity;
    item.Price = dto.Price;
    item.PriceDiscount = dto.PriceDiscount;
    }
    }
    }
    }

    SetItemPriceCommandStrategyHandler
    public class SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
    {
    private readonly IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery;

    public SetItemPriceCommandStrategyHandler(
    IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery)
    {
    if (getProductDetailsQuery == null)
    throw new ArgumentNullException("getProductDetailsQuery");

    this.getProductDetailsQuery = getProductDetailsQuery;
    }

    public void Handle(SetItemPriceCommandStrategy command)
    {
    var shoppingCartItem = command.ShoppingCartItem;

    var product = getProductDetailsQuery.Handle(new GetProductDetailsDataQuery { ProductId = shoppingCartItem.ProductId });

    // TODO: For products with custom calculations, need to use selections on shopping cart item
    // as well as custom formula and pricing points from product to calculate the item price.

    shoppingCartItem.Price = product.Price;
    }
    }

    GetProductDetailsDataQueryHandler
    public class GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
    {
    private readonly IAppContext context;

    public GetProductDetailsDataQueryHandler(IAppContext context)
    {
    if (context == null)
    throw new ArgumentNullException("context");
    this.context = context;
    }

    public ProductDetails Handle(GetProductDetailsDataQuery query)
    {
    return (from product in context.Products
    where product.Id == query.ProductId
    select new ProductDetails
    {
    Id = product.Id,
    Name = product.Name,
    Price = product.Price
    }).FirstOrDefault();
    }
    }

    SetTotalsCommandStrategyHandler
    public class SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
    {
    private readonly ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand;
    private readonly ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand;

    public SetTotalsCommandStrategyHandler(
    ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand,
    ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand
    )
    {
    if (setDiscountsCommand == null)
    throw new ArgumentNullException("setDiscountsCommand");
    if (setSalesTaxCommand == null)
    throw new ArgumentNullException("setSalesTaxCommand");

    this.setDiscountsCommand = setDiscountsCommand;
    this.setSalesTaxCommand = setSalesTaxCommand;
    }

    public void Handle(SetTotalsCommandStrategy command)
    {
    var shoppingCart = command.ShoppingCart;

    // Important: Discounts must be calculated before sales tax to ensure the discount is applied
    // to the subtotal before tax is calculated.
    setDiscountsCommand.Handle(new SetDiscountsCommandStrategy { ShoppingCart = shoppingCart });
    setSalesTaxCommand.Handle(new SetSalesTaxCommandStrategy { ShoppingCart = shoppingCart });
    }
    }

    SetDiscountsCommandStrategyHandler
    public class SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
    {
    public void Handle(SetDiscountsCommandStrategy command)
    {
    var shoppingCart = command.ShoppingCart;

    // TODO: Set discounts according to business rules

    foreach (var item in shoppingCart.Items)
    {
    item.PriceDiscount = 0;
    }

    shoppingCart.SubtotalDiscounts = 0;
    shoppingCart.SalesTaxDiscounts = 0;
    shoppingCart.ShippingDiscounts = 0;
    }
    }

    SetSalesTaxCommandStrategyHandler
    public class SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>
    {
    public void Handle(SetSalesTaxCommandStrategy command)
    {
    var shoppingCart = command.ShoppingCart;
    var postalCode = command.ShoppingCart.ShippingPostalCode;

    bool isInCalifornia = !string.IsNullOrEmpty(postalCode) ?
    // Matches 90000 to 96200
    Regex.IsMatch(postalCode, @"^9(?:[0-5]\d{3}|6[0-1]\d{2}|6200)(?:-?(?:\d{4}))?$") :
    false;

    if (isInCalifornia)
    {
    var subtotal = shoppingCart.GetSubtotal();

    // Rule for California - charge a flat 7.75% if the zip code is in California
    var salesTax = subtotal * 0.0775M;

    shoppingCart.SalesTax = salesTax;
    }
    }
    }

    请注意,此工作流中没有运送计算。这主要是因为运送计算可能取决于外部API,并且可能需要一些时间才能退货。因此,我计划使 AddToCart工作流成为添加项目时立即运行的步骤,并使 CalculateShippingAndTax工作流在从(可能是外部)源中检索到总计后再次更新UI之后发生。可能需要一些时间。

    这样可以解决问题吗?是的,它确实解决了我在命令需要依赖查询时遇到的现实问题。

    但是,感觉上这实际上只是从概念上将查询与命令分开。从物理上讲,它们仍然彼此依赖,除非您仅查看仅依赖 IDataCommandIDataQueryApplicationDbContext抽象。我不确定这是否是qujck的意图。我也不确定这是否解决了设计是否可以转移到CQRS的更大问题,但是由于这不是我计划的内容,因此我对此并不担心。

    关于asp.net-mvc - 从命令运行查询是否违反了命令-查询分隔?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36578997/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com