c# - 在一个事务中,C# 多个聚合/存储库

  显示原文与译文双语对照的内容
54 0

我有一个支付系统如下所示。付款可以通过多个礼品优惠券进行。礼品券是随购买一起发行的。顾客可以使用这里礼品券进行未来购买。

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

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

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

  • 礼品息票库
  • 付款库

代码

检索对应的GiftCoupon对象的//Use GiftCouponRepository 。

这涉及到一个事务使用两个。这是一个好的实践?如果没有,我们如何改变设计来克服这个问题?

参考: 在DDD中,聚合应该表示事务边界。需要对多个聚合进行参与的事务通常是模型应该被优化的标志,或者者应该审查事务性需求。的CQRS对我的域是正确的

enter image description here

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();
}
时间:原作者:0个回答

53 4

我想你真正想要问的是'在一个事务数据库中多个聚合'。我不认为使用多个存储库来获取事务中的数据有什么错误。通常在交易期间,聚合需要来自它的他聚合的信息,以决定是否是或者如何改变状态。但是,我认为这是你所引用的引用在一个事务中的不合适的修改。

这是不需要的原因,因为并发。除了保护它内部的变量外,每个聚合都应该是来自并发事务的protected 。比如 两个用户同时对某个聚合进行更改。

这种保护通常是通过 Having 表中的版本/时间戳来实现的。保存聚合时,对保存的版本和当前存储在 db ( 它现在可能与事务启动时不同) 中的版本进行比较。如果他们不是 MATCH,则引发异常。

它基本上归结为:协作系统( 许多用户做许多交易) 中的在单个事务中修改的越多聚合会导致并发异常增加。

如果你的聚合太大&提供了许多状态更改方法,那么 true 只能修改聚合一个。通过设计在事务中隔离修改的小聚合减少并发冲突。

Vaughn Vernon在他的3部分文章中做了一个出色的解释。

但是,这只是一个导向原则,而且需要修改多个聚合的例外情况。事实上,事务/用例是否可以以被归纳为只修改一个聚合是一件很好的事情。

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

但是当真正地分析与交易的潜在并发问题时,我不认为这真的会成为礼物优惠券聚合的冲突。它们只被创建为( 颁发),然后用于支付。没有其他状态更改操作在两者之间。因这里在这个实例中我们不需要关心这个事实,我们同时修改付款/订单&优惠券。

下面是我快速建立模型的一个可能的方法

  • 我无法看到付款如何没有订单集合的意义,所以我引入了一个。
  • 订单是由付款组成的。可以用礼品优惠券进行付款。你可以创建其他类型的付款,例如CashPayment或者 CreditCardPayment,例如。
  • 为了制作礼品券付款,优惠券集合必须通过订单集合。然后将优惠券标记为已经使用。
  • 在交易结束时,订单集合与新的payment(s), 保存,所有使用的礼品优惠券也保存。

代码:

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;
 }
}
原作者:
...