如何将现有的 class 转换为包含事件的 DDD 聚合?
How do I convert an existing class to a DDD aggregate with events?
我下面有以下class,用于执行买卖订阅的交易操作。我想转换这个 class 以便它可以在使用事件源和可能的 CQRS 的微服务中使用。我的想法是,这将存在于 Service Fabric Actor 内部,其中 class 将完全在内存中。
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 方法以便其他 services/subscribers 可以收到交易发生通知?
我觉得我完全错误地考虑了这个问题,因为认为聚合应该是短暂的并且不会像这个那样长寿。如果有任何建议或指导,我将不胜感激。
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;
}
}
如果我没有误会你的话,你试图做的是聚合对自身引发的事件做出反应。
没有意义。聚合在事务上是一致的,因此您可以在一个事务中完成所有操作,而无需使用事件。
事件是为了同一 BC 的不同聚合之间或两个 BC 之间的最终一致性,因为在事务中只有一个聚合的状态被修改。聚合必须通过引发事件通知其事务边界之外的世界发生了某些事情。
我认为也许您应该查看您的域以检查是否必须将聚合拆分为多个。如果是这样,请使用事件以异步方式传达它们。否则(只有一个聚合),您不需要监听事件,只需引发它们以将它们存储在事件存储中。
我下面有以下class,用于执行买卖订阅的交易操作。我想转换这个 class 以便它可以在使用事件源和可能的 CQRS 的微服务中使用。我的想法是,这将存在于 Service Fabric Actor 内部,其中 class 将完全在内存中。
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 方法以便其他 services/subscribers 可以收到交易发生通知?
我觉得我完全错误地考虑了这个问题,因为认为聚合应该是短暂的并且不会像这个那样长寿。如果有任何建议或指导,我将不胜感激。
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;
}
}
如果我没有误会你的话,你试图做的是聚合对自身引发的事件做出反应。
没有意义。聚合在事务上是一致的,因此您可以在一个事务中完成所有操作,而无需使用事件。
事件是为了同一 BC 的不同聚合之间或两个 BC 之间的最终一致性,因为在事务中只有一个聚合的状态被修改。聚合必须通过引发事件通知其事务边界之外的世界发生了某些事情。
我认为也许您应该查看您的域以检查是否必须将聚合拆分为多个。如果是这样,请使用事件以异步方式传达它们。否则(只有一个聚合),您不需要监听事件,只需引发它们以将它们存储在事件存储中。