在不产生事件的聚合中使用 Commanded 避免内存泄漏

Avoiding memory leaks with Commanded in an aggregate that doesn't produce an event

在 Elixir 1.7.4 上使用 Commanded 0.17.2 构建的应用程序经常 运行 内存不足。调查发现内存泄漏似乎是由不断增加的聚合实例引起的,这些实例从未停止。

相关聚合接收由外部系统触发的命令。在某些情况下,execute 函数 returns 一个事件,而在其他一些情况下,该命令应该被忽略,因此它 returns nil (如 docs).

def execute(%RemoteThing{}, %ImportRemoteThing{deleted: true}), do: nil

似乎每次返回 nil 而不是事件时,聚合实例都会无限期地保持活动状态。即使同时附加了超时和生命周期,也会发生这种情况,这明确表示有其他意图:

defmodule RemoteThing.Lifespan do
  @behaviour Commanded.Aggregates.AggregateLifespan

  def after_event(_event), do: :stop
  def after_command(_command), do: :stop
end

dispatch(
  ImportRemoteThing,
  to: RemoteThing,
  lifespan: RemoteThing.Lifespan,
  timeout: 15_000
)

我怀疑这是 Commanded 中的错误:

defp aggregate_lifespan_timeout(_context, []), do: :infinity

避免内存泄漏的一种方法是生成一个事件,即使没有人需要它。这将导致污染的持久事件存储而不是易失性内存,因此可能会在长期 运行.

中导致更大的问题。

我现在正在寻找一种方法来停止聚合实例,以防 execute 函数 returns 和 nil。非常感谢任何解决方法的想法。

此问题已修复,将在 next release of Commanded 中提供。

拉取请求 #210 扩展聚合生命周期行为以包括 after_error/1after_command/1 回调。

以前您只需定义一个 after_event/1 回调函数来实现 Commanded.Aggregates.AggregateLifespan 行为:

defmodule BankAccountLifespan do
  @behaviour Commanded.Aggregates.AggregateLifespan

  def after_event(%BankAccountClosed{}), do: :stop
  def after_event(_event), do: :infinity
end

现在您还必须定义 after_command/1after_error/1 回调函数:

defmodule BankAccountLifespan do
  @behaviour Commanded.Aggregates.AggregateLifespan

  def after_event(%BankAccountClosed{}), do: :stop
  def after_event(_event), do: :infinity

  def after_command(%CloseAccount{}), do: :stop
  def after_command(_command), do: :infinity

  def after_error(:invalid_initial_balance), do: :stop
  def after_error(_error), do: :stop
end

after_command/1 回调将允许您在没有生成任何事件时停止聚合。