多聚集/存储库中的一个交易(Multiple Aggregates / Repositories i

2019-06-14 21:00发布

我有一个支付系统,如下图所示。 付款可以通过多种礼券进行。 该礼券是与购买一起被发布。 客户可以为未来的购买使用该礼券的。

当支付是通过赠券制成,GiftCoupon表中的列UsedForPaymentID需要与PaymentID(用于giftcoupon ID)进行更新。

该GiftCouponIDs已经在数据库中可用。 当客户产生礼券,它已经GiftCouponID打印就可以了。 运营商需要输入这个CouponID对系统进行支付。

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

  1. 礼品券库
  2. 收支库

//使用GiftCouponRepository以获取相应的GiftCoupon对象。

这涉及到使用两种存储库的一个交易。 这是一个好的做法呢? 如果没有,我们如何改变设计来克服这个?

参考 :在DDD总结应该代表事务边界。 需要不止一个总的参与事务常常是一个标志,无论是模型要细化,或事务需求进行审查,或两者兼而有之。 是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();
}

Answer 1:

我想你的真正用意。问了关于“ 在一个事务中多聚集 ”。 我不相信有什么毛病使用多个存储库在事务中取数据 。 通常,一个交易过程中的聚集需要,以作出是否或如何,改变状态决定从其他聚集信息。 没关系。 它,然而,国家在一个事务中的多个聚集的是不被认可的修改,我认为这是什么您参考报价试图暗示。

之所以这样是不可取的,因为并发。 以及保护在变体中它的边界,每个聚集应防止并发事务。 例如,两个用户使在同一时间更改的集合。

这种保护一般是通过具有在聚集体DB表的一个版本/时间戳来实现。 当骨料被保存,进行比较时的版本被保存和当前版本存储在数据库中(现在可能是从当交易开始有所不同)。 如果它们不匹配的异常。

它基本上可以归结为这样的: 在一个协同系统(许多用户执行许多交易),即在一个单一的交易修改的更聚集会导致增加并发异常。

如果您的累计过大及提供多种状态改变的方法同样的事情是真实的; 多个用户只能修改一次的总之一。 通过设计修改隔离在一个事务中降低并发性冲突小聚集。

沃恩弗农在他的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;
    }
}


Answer 2:

没有什么错在一个事务中使用两个库。 作为JB Nizet指出,这是一个服务层是什么。

如果您有任何问题,保持连接共享,您可以使用工作单元 1模式来控制从服务层的连接,并有工厂,提供给你的资料库提供OOW实例的数据上下文。

1 EF / L2S DataContext的本身就是一个UOW实现,但它是很好的有一个抽象一个用于诸如这些情况下,服务层。



Answer 3:

我想提出将答案“这取决于”(TM),因为它归结为是“足够好”

问题空间和技术实现两者的情况下不为人所熟知,并会影响任何可接受的解决方案。

如果技术允许它(说在ACID数据存储),那么它可能是有意义从业务的角度来使用事务。

如果技术不提供这些功能,那么它可能是有意义的,以“锁定”所有的优惠券和支付记录的更新是一致的。 多久锁的,什么争可能会出现需要进行调查。

第三,它可以被实现为以下粗略的业务流程策略的多个交易/聚集。

注:我没有定义如何的相互作用骨料之间发生的技术要求是不知道

  1. “创建”第一合计(姑且称之为购买骨料),这将记录哪些标识优惠券使用的预期付款。
  2. 尽可能晚,确认当前的业务策略是有效的(每张赠券是当前有效)。 如果不是,取消/停止商业交易。
  3. 坚持一个“暂定”状态的购买总和。
  4. 每个优惠券的总量暂定购买到“调整上限”进行互动。 在回信中成功/失败。
  5. 在“调整极限”将改变资金可用金额,可用于其他潜在购买骨料
  6. 如果有任何的优惠券不能“调整极限”,那么购买的“被取消”并批准了该优惠券限制重新调整回购前要求量(现在购买是在“取消”州)
  7. 如果所有的优惠券限制进行调整,那么购买现在处于“敲定”状态
  8. 在“敲定”状态,系统现在每张赠券总交互,以“完成优惠券使用”,其中,可能的话,购买优惠券使用上的优惠券总轴颈(依赖于业务逻辑和需求)
  9. 一旦所有的优惠券用途已经定稿,那么购买骨料被设置为“已批准”和任何其他的业务流程可以开始的状态。

很多您的选择将取决于什么是从业务和技术能力的角度正确。 每个选择的赞成的和反对的影响企业的成功,无论是现在还是将来。 '这取决于'(TM)



Answer 4:

方法2:

  • 两个独立的交易。 如果事务2失败,则交易1应该是回滚。
  • 卡是一个帐户。 针对该帐户记录交易。 如果calcuated平衡(合计所有交易)降为零(或更小,应该不会发生),那么卡是在DB“used'-别录‘拿来主义’虽然。 单从资产负债得到它。


文章来源: Multiple Aggregates / Repositories in one Transaction