设计Akka.NET领域事件和命令的最佳实践 | Petabridge


这是一篇.NET中Akka的领域事件和命令设计文章,阐述如何通过设计事件使Akka.NET编程更容易。详细点击标题见原文:

1. 慷慨地使用标记/身份接口
如果我们有大量的域事件用于交易股票,所有这些事件都有几个常见的标识符和属性,对于路由,分片或分发这些消息非常有用:

  1. 股票代码(MSFT,TEAM,AMD等......);
  2. 订单的ID; 
  3. 它们都代表由于交易者活动而发生的实时交易事件 - 这与交易所发出的事件不同,该事件表明特定股票代码的最新“市场价格”是什么。

2. 始终使您的域消息不可变
这是因为如果您向同一进程内的本地运行的许多actor发送相同的消息 - 所有这些actor都会收到对该消息的同一副本的引用。如果您使此消息类型变为可变,如果一个actor修改此消息上的任何属性或字段,那些更改将传播到处理相同消息的所有其他actor - 这就是所谓的在并发编程中“副作用” 。
因此,我们需要确保默认情况下在actor之间发送的所有域消息都是不可变的。以下是如何做到这一点:

  1. 将所有属性和字段设置为只读 - 可以通过消息的构造函数设置值的唯一方法;
  2. 切勿将正常集合,即List<T>或Dictionary<K,V>,作为一个消息类的属性-总是暴露System.Collections.Generic集合为IReadOnlyCollection<T>,IReadOnlyList<T>,IReadOnlyDictionary<K,V>,等;
  3. 确保传递给消息构造函数的对象本身是不可变的。

3. 可以轻松将域实体复制到不可变消息中
根据我们的“如何使您的消息不可变”清单中的第3项,确保您的actor通过不可变消息与其他actor共享的数据也是不可变的本身是一种很好的做法。
我们将所有内部状态复制到新集合中并将其返回到不可变消息中。

4. 将ToString()方法重写为漂亮输出领域事件
在对生产进行故障排除时,Akka.NET系统开发人员通常依赖于Akka.NET的日志记录基础设施和Phobos actor跟踪等工具- 但从这些系统中获取良好信息通常需要详细打印出在整个系统中传播的关键消息的内容。
管理此方法的最简单方法是覆盖object.ToString()每个域事件上的方法,并将其自定义为“非常打印”事件的状态。

/// <summary>
/// Concrete <see cref="IPriceUpdate"/> implementation.
/// </summary>
public sealed class PriceChanged : IPriceUpdate, IComparable<PriceChanged>
{
    public PriceChanged(string stockId, decimal currentAvgPrice, DateTimeOffset timestamp)
    {
        StockId = stockId;
        CurrentAvgPrice = currentAvgPrice;
        Timestamp = timestamp;
    }

    public DateTimeOffset Timestamp { get; }

    public decimal CurrentAvgPrice { get; }

    public string StockId { get; }

    public int CompareTo(PriceChanged other)
    {
        if (ReferenceEquals(this, other)) return 0;
        if (ReferenceEquals(null, other)) return 1;
        return Timestamp.CompareTo(other.Timestamp);
    }

    public int CompareTo(IPriceUpdate other)
    {
        if (other is PriceChanged c)
        {
            return CompareTo(c);
        }
        throw new ArgumentException();
    }

    public override string ToString()
    {
        return $
"[{StockId}][{Timestamp}] - $[{CurrentAvgPrice}]";
    }
}

当您遵循这种方法时,所有关于如何将域事件呈现为文本的逻辑都会成为消息本身的一部分,以及许多不同的基础结构,这些基础结构可能都需要打印出消息的内容(日志记录,异常)处理等...如果ToString()正确实施,都可以使用一致的显示格式。