代码之家  ›  专栏  ›  技术社区  ›  Ryan

如何将现有类转换为包含事件的DDD聚合?

  •  1
  • Ryan  · 技术社区  · 7 年前

    public class OrderBook
    {
        public const int ScaleFactor = 10_000;
        private long _orderId = 1;
    
        public OrderBook()
        {
            Limits = new RankedSet<Limit>(new LimitPriceComparer()) { new Limit { Price = 1 * ScaleFactor } };
            Subscriptions = new Dictionary<long, Subscription>();
            Orders = new Dictionary<long, Order>();
        }
    
        private RankedSet<Limit> Limits { get; }
        private IDictionary<long, Subscription> Subscriptions { get; }
        private IDictionary<long, Order> Orders { get; }
    
        public Order Ask(long userId, long price, int shares)
        {
            if (userId <= 0 || price <= 0 || shares <= 0)
            {
                // TODO: Return a message or something.
                return null;
            }
    
            price = price * ScaleFactor;
    
            // Get the users subscription.
            if (!Subscriptions.TryGetValue(userId, out Subscription subscription))
            {
                // TODO: Return a message or something.
                return null;
            }
    
            var index = Limits.Count - 1;
            var originalShares = shares;
            while (index >= 0 && shares > 0)
            {
                var currentLimit = Limits.ElementAt(index);
                if (currentLimit.Price < price)
                {
                    break;
                }
    
                Order order = currentLimit.BidHead;
                while (order != null && shares > 0)
                {
                    if (order.Subscription.UserId == userId)
                    {
                        if (order.Next == null)
                        {
                            break;
                        }
                        else
                        {
                            order = order.Next;
                        }
                    }
    
                    // Always assume the bid will have a subscription even if it's empty.
                    if (order.Shares >= shares)
                    {
                        order.Subscription.Owned += shares;
                        order.Shares -= shares;
                        shares = 0;
                    }
                    else
                    {
                        order.Subscription.Owned += order.Shares;
                        shares -= order.Shares;
                        order.Shares = 0;
                    }
    
                    order = order.Next;
                }
    
                index--;
            }
    
            if (shares > 0)
            {
                subscription.Owned -= originalShares - shares;
    
                var newOrder = new Order { Id = /*Interlocked.Increment(ref _orderId)*/_orderId++, Shares = shares, Subscription = subscription };
    
                // At this point Limits is guaranteed to have a single Limit.
                var prevLimit = Limits.ElementAt(index == Limits.Count - 1 ? index : ++index);
                if (prevLimit.Price == price)
                {
                    newOrder.ParentLimit = prevLimit;
                    if (prevLimit.AskHead == null)
                    {
                        prevLimit.AskHead = newOrder;
                    }
                    else
                    {
                        newOrder.Next = prevLimit.AskHead;
                        prevLimit.AskHead.Prev = newOrder;
                        prevLimit.AskHead = newOrder;
                    }
                }
                else
                {
                    var newLimit = new Limit { AskHead = newOrder, Price = price };
                    newOrder.ParentLimit = newLimit;
                    Limits.Add(newLimit);
                }
    
                Orders.Add(newOrder.Id, newOrder);
                return newOrder;
            }
            else
            {
                subscription.Owned -= originalShares;
            }
    
            return null;
        }
    }
    

    下面是我认为转换为聚合的一个开始。我遇到的问题是,当引发TradeExecutedEvent时,它需要整体修改聚合的状态。换句话说,如果该事件是自己触发的,那么它就没有意义,因为它依赖于它之前发生的事件。我认为需要TradeExecutedEvent的唯一原因是在UI上通知他们的交易已经执行。

    是否仍将TradeExecutedEvent存储在事件存储中,但不为其提供相应的Apply方法,以便通知其他服务/订阅者发生了交易?

    在我看来,我认为这是完全错误的,因为我认为聚合应该是短暂的,不会像这个一样长寿。如有任何建议或指导,我将不胜感激。

    public class TradeAggregate : AggregateBase
    {
        private const int ScaleFactor = 10_000;
        private RankedSet<Limit> Limits { get; }
        private IDictionary<long, Subscription> Subscriptions { get; }
        private IDictionary<long, Order> Orders { get; }
    
        public TradeAggregate(string asset)
        {
            Limits = new RankedSet<Limit>(new LimitPriceComparer()) { new Limit { Price = 1 * ScaleFactor } };
            Subscriptions = new Dictionary<long, Subscription>();
            Orders = new Dictionary<long, Order>();
        }
    
        public void Ask(long userId, long price, int shares)
        {
            if (userId <= 0 || price <= 0 || shares <= 0)
            {
                // TODO: Return a message or something.
                return;
            }
    
            price = price * ScaleFactor;
    
            if (!Subscriptions.TryGetValue(userId, out Subscription subscription))
            {
                throw new System.Exception("You do not own this subscription.");
            }
    
            RaiseEvent(new AskOrderPlacedEvent(subscription, price, shares));
        }
    
        public void Apply(AskOrderPlacedEvent e)
        {
            var index = Limits.Count - 1;
            var shares = e.Shares;
            while (index >= 0 && shares > 0)
            {
                var currentLimit = Limits.ElementAt(index);
                if (currentLimit.Price < e.Price)
                {
                    break;
                }
    
                Order order = currentLimit.BidHead;
                while (order != null && shares > 0)
                {
                    if (order.Subscription.UserId == e.Subscription.UserId)
                    {
                        if (order.Next == null)
                        {
                            break;
                        }
                        else
                        {
                            order = order.Next;
                        }
                    }
    
                    // Always assume the bid will have a subscription even if it's empty.
                    if (order.Shares >= shares)
                    {
                        RaiseEvent(new TradePartiallyExecutedEvent(order, shares, e.Subscription, e.Shares));
                        shares = 0;
                    }
                    else
                    {
                        RaiseEvent(new TradeExecutedEvent(order, shares, e.Subscription, e.Shares));
                        shares -= order.Shares;
                    }
    
                    order = order.Next;
                }
    
                index--;
            }
    
            if (shares > 0)
            {
                // .... etc.
            }
            else
            {
                // .... etc.
            }
        }
    
        public void Apply(TradePartiallyExecutedEvent e)
        {
            e.Order.Subscription.Owned += e.Shares;
            e.Order.Shares -= e.Shares;
            e.Subscription.Owned -= e.OriginalShares - e.Shares;
        }
    
        public void Apply(TradeExecutedEvent e)
        {
            e.Order.Subscription.Owned += e.Order.Shares;
            e.Order.Shares = 0;
            e.Subscription.Owned -= e.OriginalShares;
        }
    }
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   choquero70    7 年前

    如果我不理解您的意思,您试图做的是聚合对自身引发的事件做出反应。

    这毫无意义。聚合在事务上是一致的,因此您可以在一个事务中执行所有操作,而无需使用事件。

    事件用于同一BC的不同聚合之间或两个BC之间的最终一致性,因为在事务中只修改一个聚合的状态。聚合必须向其交易边界以外的世界通知,通过引发事件发生了某些事情。