首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >一个事务中的多个集合/存储库

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

Stack Overflow用户
提问于 2012-07-12 05:46:46
回答 3查看 7.9K关注 0票数 15

我有如下所示的支付系统。付款可以通过多种礼券进行。礼品券是在购买的同时发放的。顾客可利用此礼券作日后购买用途。

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

GiftCouponID已经在数据库中可用。当顾客生产礼品优惠券时,它会印上GiftCouponID。操作员需要将此CouponID输入系统以进行支付。

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

  1. 礼券库房
  2. 付款仓库

代码

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

这涉及到为一个事务使用两个存储库。这是一个很好的练习吗?如果没有,我们如何才能改变设计以克服这一问题?

引用:在DDD中,聚合应该表示事务边界。一个需要多个聚合参与的事务通常是一个迹象,表明要么应该改进模型,要么应该检查事务需求,或者两者都需要。CQRS对我的域名正确吗?

C#代码

代码语言:javascript
运行
复制
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();
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-07-12 12:59:05

我认为您真正想问的是关于“Multiple Aggregates in one transaction”的问题。我不认为使用多个存储库在事务中获取数据是有什么问题的。通常,在事务处理期间,聚合将需要来自其他聚合方的信息,以决定是否更改状态或如何更改状态。这很好。然而,在一个事务中修改多个聚合的状态被认为是不可取的,我认为这正是您引用的引用试图暗示的。

这是不可取的原因是由于并发。除了保护其边界内的内部变量外,还应保护每个聚合不受并发事务的影响。例如,两个用户同时对一个集合进行更改。

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

基本上可以归结为:协作系统中的(许多用户进行了许多事务),单个事务中修改的聚合越多,并发异常就会增加。

如果聚合太大,同样的情况也是一样的&它提供了许多状态更改方法;多个用户一次只能修改一个聚合。通过设计在事务中单独修改的小聚合,可以减少并发冲突。

沃恩·弗农( Vaughn Vernon )在他的第三篇文章中对此做了出色的解释。

然而,这只是一项指导原则,在需要修改一个以上的集合时会有例外情况。您正在考虑是否可以将事务/用例重新分解为只修改一个聚合,这是一件好事。

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

但当真正分析这项交易的潜在并发性问题时,我不认为在礼品券总额上会发生冲突。它们只会被创建(发行)然后用于支付。之间没有其他状态更改操作。因此,在这种情况下,我们不需要担心这个事实,我们正在修改支付/订购和礼品优惠券的总和。

下面是我快速想出的一种可能的建模方法

  • 如果没有付款的订单汇总,我看不出付款有什么意义,所以我介绍了一个。
  • 订单是由付款组成的。可以用礼券付款。您可以创建其他类型的支付,例如CashPayment或CreditCardPayment。
  • 若要支付礼品优惠券,必须将优惠券总额传递给订单汇总。这标志着优惠券的使用。
  • 在交易结束时,订单汇总将与其新付款一起保存,并且使用的任何礼品优惠券也会保存。

代码:

代码语言:javascript
运行
复制
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;
    }
}
票数 33
EN

Stack Overflow用户

发布于 2015-03-03 23:56:01

我将提交的答案是“它取决于”(Tm),因为它归根结底是“足够好”。

问题空间和技术实现的背景都不为人所知,将影响到任何可接受的解决方案。

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

如果这些技术没有提供这些功能,那么可能有必要“锁定”所有的优惠券和支付记录,以使更新保持一致。需要对锁的时间和可能发生的争用进行调查。

第三,它可以通过以下粗糙的业务流程策略作为多个事务/聚合来实现。

注意:我没有定义聚合之间的交互是如何发生的,因为不知道技术需求。

  1. “创建”第一个集合(我们将其命名为购买汇总),它将记录预期的付款,以确定要使用的优惠券。
  2. 尽可能晚,确认当前的业务策略是有效的(每个优惠券都是有效的)。如果没有,请取消/停止业务事务。
  3. 将购买总量保持在“暂时”状态。
  4. 与每个优惠券汇总互动,以“调整限额”的暂定购买。回答成功/失败。
  5. “调整限额”将改变其他潜在购买总额的可用金额。
  6. 如果任何一张优惠券没有“调整限额”,那么该购买将被“取消”,而经批准的优惠券限额将被重新调整回购买前的请求金额(而该购买现在处于“取消”状态)。
  7. 如果所有的优惠券限制都被调整,那么购买就处于“最后确定”状态。
  8. 在“最后确定”状态中,系统现在与每个优惠券聚合进行交互,以“最后确定优惠券的使用”,其中可能会将购买的优惠券使用情况记录在优惠券聚合上(取决于业务逻辑和需求)。
  9. 一旦所有的优惠券使用已经最后确定,然后购买总量被设置为‘批准’的状态,任何额外的业务流程都可以开始。

从业务和技术能力的角度来看,很多选择将取决于什么是正确的。无论是现在还是将来,每种选择的利弊都会影响到企业的成功。“这取决于”(Tm)

票数 0
EN

Stack Overflow用户

发布于 2015-06-22 23:33:25

2办法:

  • 两笔单独的交易。如果事务2失败,那么事务1应该回滚.
  • 卡是一个帐户。将交易记录在该帐户上。如果计算余额(所有事务的总和)达到零(或更少,不应该发生),那么卡是‘使用’-但不要记录‘使用’在DB。只要从平衡中得到它。
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11445657

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档