当前位置: 首页 > 编程学习 > 软件工程 > 领域驱动 > 正文

用DDD设计一个电商网站(十三) 领域事件扩展

2018-04-22 来源:博客园/Zachary_Fan

一、前言

上篇中我们初步运用了领域事件,其中还有一些问题我们没有解决,所以实现是不健壮的,下面先来回顾一下。

二、回顾

先贴一下上篇中的遗留的问题:

        public Result Create(OrderRequest orderRequest)
        {
            if (!string.IsNullOrWhiteSpace(orderRequest.CouponId))
            {
                var couponResult = DomainRegistry.SellingPriceService().IsCouponCanUse(orderRequest.CouponId, orderRequest.OrderTime);
                if (!couponResult.IsSuccess)
                    return Result.Fail(couponResult.Msg);
            }
 
            var orderId = DomainRegistry.OrderRepository().NextIdentity();
            var order = Domain.Order.Aggregate.Order.Create(orderId, orderRequest.UserId, orderRequest.Receiver,
                orderRequest.CountryId, orderRequest.CountryName, orderRequest.ProvinceId, orderRequest.ProvinceName,
                orderRequest.CityId, orderRequest.CityName, orderRequest.DistrictId, orderRequest.DistrictName,
                orderRequest.Address, orderRequest.Mobile, orderRequest.Phone, orderRequest.Email,
                orderRequest.PaymentMethodId, orderRequest.PaymentMethodName, orderRequest.ExpressId,
                orderRequest.ExpressName, orderRequest.Freight, orderRequest.CouponId, orderRequest.CouponName, orderRequest.CouponValue, orderRequest.OrderTime);
 
            foreach (var orderItemRequest in orderRequest.OrderItems)
            {
                order.AddOrderItem(orderItemRequest.ProductId, orderItemRequest.Quantity, orderItemRequest.UnitPrice, orderItemRequest.JoinedMultiProductsPromotionId, orderItemRequest.ProductName);
            }
 
            DomainRegistry.OrderRepository().Save(order);
            DomainEventBus.Instance().Publish(new OrderCreated(order.ID, order.UserId, order.Receiver));
            return Result.Success();
        }

不知道大家有没有发现这里代码上的一个问题,就是DomainEventBus.Instance().Publish()方法在聚合的Save操作之后进行,其实本身不是很符合DDD的概念,任何的领域事件都是基于一个领域对象的,没有领域对象何来领域事件,所以领域事件一般都是由领域对象内部产生,故这里应该要把DomainEventBus.Instance().Publish()方法搬到Order.Create中调用。如果发现这个问题的童鞋,恭喜你对于领域事件的理解已经又深入了一个层次了。好了上篇中这么写其实是为了凸显出本地数据修改提交和领域事件的发布是涉及到数据一致性的问题的,其中的问题是:

1.如果领域事件发布出现异常了怎么办?

2.如果订阅者处理出现异常了怎么办?

本篇我们就来一个一个解决问题。

三、本地的一致性

在解决上面的2个问题之前,我们先需要考虑在修改多个聚合的场景下本地上下文内的一致性问题,这个职责在DDD中由工作单元(UnitOfWork)来负责,工作单元就是为了保证本地的事务一致性,在.Net里的实现一般就是对SqlTransaction的封装运用。关于工作单元的实现一般有2种方式:

(1)完全依赖于SqlTransaction,在工作单元第一次运用的时候就开启数据库事务。

(2)使用本地变量存储变动的聚合,然后在工作单元Commit()的时候开启数据库事务并写入。

2个实现方案各有优缺点,需要在一致性和性能之间做出权衡。另外工作单元和领域事件发布的结合运用可以参考我之前写的2篇文章:DDD设计中的Unitwork与DomainEvent如何相容?和DDD中的Unitwork与DomainEvent如何相容?(续),注意的是我在这2篇中运用的是方式(2)的实现方式。秉着没有最好只有更好的精神,如何才能做到更好的一致性,这里需要引出几个架构层面的概念:ES、Saga、A+ES。这些内容有一篇蟋蟀兄的文章(传送门在此)讲的很好,推荐大家阅读一下,我就不展开讲这些内容了。里面每一种方案的运用都有成本,大家根据实际情况权衡再运用即可,切记:软件开发中没有银弹。