一个事务中的多个聚合/存储库

LCJ*_*LCJ 12 c# oop design-patterns domain-driven-design cqrs

我有一个支付系统,如下所示.付款可以通过多个礼券进行.礼品券与购买一起发行.客户可以使用此礼品券以备将来购买.

当通过礼品券进行付款时,GiftCoupon表中的UsedForPaymentID列需要使用该PaymentID(对于礼品券ID)进行更新.

GiftCouponID已在数据库中提供.当客户生产礼品券时,它上面印有GiftCouponID.运营商需要将此CouponID输入系统以进行付款.

对于MakePayment()操作,它需要两个存储库.

  1. 礼品券库
  2. 付款存储库

//使用GiftCouponRepository检索相应的GiftCoupon对象.

这涉及为一个事务使用两个存储库.这是一个好习惯吗?如果没有,我们如何改变设计来克服这个问题呢?

参考:在DDD中,Aggregate应代表事务边界.需要涉及多个聚合的交易通常表明应该改进模型,或者应该审查交易要求,或者两者兼而有之.CQRS对我的域名是否正确?

在此输入图像描述

C#代码

public RepositoryLayer.ILijosPaymentRepository repository { get; set; }

public void MakePayment(int giftCouponID)
{
    DBML_Project.Payment paymentEntity = new DBML_Project.Payment();
    paymentEntity.PaymentID = 1;

    DBML_Project.GiftCoupon giftCouponObj;

    //Use GiftCouponRepository to retrieve the corresponding GiftCoupon object.     

    paymentEntity.GiftCouponPayments = new System.Data.Linq.EntitySet<DBML_Project.GiftCoupon>();
    paymentEntity.GiftCouponPayments.Add(giftCouponObj);

    repository.InsertEntity(paymentEntity);
    repository.SubmitChanges();
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*ers 29

我认为你真正要问的是" 一次交易中多重聚合 ".我不认为使用多个存储库来获取事务中的数据有任何问题.通常在交易期间,聚合将需要来自其他聚合的信息,以便决定是否或如何改变状态.没关系.但是,在一个事务中,多个聚合上的状态修改被认为是不合需要的,我认为这是您引用的引用所暗示的内容.

这是不可取的原因是因为并发.除了保护其边界内的in-variants外,还应保护每个聚合不受并发事务的影响.例如,两个用户同时对聚合进行更改.

通常通过在聚合的DB表上具有版本/时间戳来实现此保护.保存聚合时,将比较正在保存的版本和当前存储在db中的版本(现在可能与事务启动时不同).如果它们不匹配则引发异常.

它基本归结为:在协作系统(许多用户进行许多事务)中,在单个事务中修改的聚合越多,将导致并发异常的增加.

如果您的聚合太大并且提供许多状态改变方法,则完全相同的事情是正确的; 多个用户一次只能修改一个聚合.通过设计在事务中单独修改的小聚合可以减少并发冲突.

Vaughn Vernon在他的3篇文章中做了很好的解释.

但是,这只是一个指导原则,例外情况是需要修改多个聚合.您正在考虑是否可以重新考虑事务/用例以仅修改一个聚合这一事实是一件好事.

考虑过您的示例后,我无法想到将其设计为满足事务/用例要求的单个聚合的方法.需要创建付款,并且需要更新优惠券以表明它不再有效.

但是,在真正分析此次交易的潜在并发性问题时,我认为礼券优惠券实际上不会发生冲突.它们只是创建(发行)然后用于付款.两者之间没有其他状态改变操作.因此,在这种情况下,我们不需要担心我们正在修改付款/订单和礼品优惠券汇总的事实.

以下是我很快想出的一种可能的建模方法

  • 如果没有付款所属的订单汇总,我无法看到付款是否合理,所以我介绍了一个.
  • 订单由付款组成.可以使用礼券进行付款.您可以创建其他类型的付款,例如CashPayment或CreditCardPayment.
  • 要进行礼品券付款,优惠券总量必须传递到订单汇总.然后,这标记使用的优惠券.
  • 在交易结束时,订单汇总将与其新付款一起保存,并且还会保存使用的任何礼品券.

码:

public class PaymentApplicationService
{
    public void PayForOrderWithGiftCoupons(PayForOrderWithGiftCouponsCommand command)
    {
        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
            Order order = _orderRepository.GetById(command.OrderId);

            List<GiftCoupon> coupons = new List<GiftCoupon>();

            foreach(Guid couponId in command.CouponIds)
                coupons.Add(_giftCouponRepository.GetById(couponId));

            order.MakePaymentWithGiftCoupons(coupons);

            _orderRepository.Save(order);

            foreach(GiftCoupon coupon in coupons)
                _giftCouponRepository.Save(coupon);
        }
    }
}

public class Order : IAggregateRoot
{
    private readonly Guid _orderId;
    private readonly List<Payment> _payments = new List<Payment>();

    public Guid OrderId 
    {
        get { return _orderId;}
    }

    public void MakePaymentWithGiftCoupons(List<GiftCoupon> coupons)
    {
        foreach(GiftCoupon coupon in coupons)
        {
            if (!coupon.IsValid)
                throw new Exception("Coupon is no longer valid");

            coupon.UseForPaymentOnOrder(this);
            _payments.Add(new GiftCouponPayment(Guid.NewGuid(), DateTime.Now, coupon));
        }
    }
}

public abstract class Payment : IEntity
{
    private readonly Guid _paymentId;
    private readonly DateTime _paymentDate;

    public Guid PaymentId { get { return _paymentId; } }

    public DateTime PaymentDate { get { return _paymentDate; } }

    public abstract decimal Amount { get; }

    public Payment(Guid paymentId, DateTime paymentDate)
    {
        _paymentId = paymentId;
        _paymentDate = paymentDate;
    }
}

public class GiftCouponPayment : Payment
{
    private readonly Guid _couponId;
    private readonly decimal _amount;

    public override decimal  Amount
    {
        get { return _amount; }
    }

    public GiftCouponPayment(Guid paymentId, DateTime paymentDate, GiftCoupon coupon)
        : base(paymentId, paymentDate)
    {
        if (!coupon.IsValid)
            throw new Exception("Coupon is no longer valid");

        _couponId = coupon.GiftCouponId;
        _amount = coupon.Value;
    }
}

public class GiftCoupon : IAggregateRoot
{
    private Guid _giftCouponId;
    private decimal _value;
    private DateTime _issuedDate;
    private Guid _orderIdUsedFor;
    private DateTime _usedDate;

    public Guid GiftCouponId
    {
        get { return _giftCouponId; }
    }

    public decimal Value
    {
        get { return _value; }
    }

    public DateTime IssuedDate
    {
        get { return _issuedDate; }
    }

    public bool IsValid
    {
        get { return (_usedDate == default(DateTime)); }
    }

    public void UseForPaymentOnOrder(Order order)
    {
        _usedDate = DateTime.Now;
        _orderIdUsedFor = order.OrderId;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 同意但不是这样:"在单个事务中修改的聚合越多,将导致并发异常的增加." 我宁愿说一次修改多个聚合会产生错误的不变量,但不会产生更多的并发异常,如果不是这种情况就会发生.例如,如果我在同一事务中更新聚合A和聚合B,如果B由于乐观并发异常而失败,则A也失败.但是当A和B在分离的事务中更新时,即使B由于B并发更新而失败,A也会成功. (2认同)
  • 我可能会遗漏一些东西,但你的陈述似乎完全支持我引用的句子?是的,如果在单独的事务中更新两个聚合,那么可以将并发失败的可能性降低一半.那就是我在说什么?正如你所说:如果你在同一个事务中修改它们而一个失败,它们都会失败.因此:在事务中修改的聚合越多,导致并发异常率越高......? (2认同)
  • 是的,我认为它会更清楚:)顺便说一句,好帖子的答案:) (2认同)