Akka持久演员-功能方法

时间:2019-11-24 21:08:41

标签: scala functional-programming akka actor

我一直在尝试使用功能性方法来实现持久性参与者-我的意思是根本没有任何变量。但我遇到麻烦了:)下面的代码示例无法正常工作,因为处理程序参数未在处理程序之间共享(receiveCommand / receiveRecover)。两者都从零开始,然后彼此覆盖-重播某些事件后,命令处理程序仍将处于起点。

此实现的另一个问题是在同一位置处理命令和事件的事实

以功能方式进行操作是否是一种好习惯?

class Item(sku: String) extends PersistentActor with ActorLogging {
    import Item._
    override def persistenceId: String = sku
    override def receiveCommand: Receive = handler(0, 0)
    override def receiveRecover: Receive = handler(0, 0)

    def handler(quantity: Int, booked: Int): Receive = {
      case Increase(q) =>
        val event = StockChanged(sku, q)
        persist(event)(e => context.become(handler(quantity + e.quantity, booked)))
      case Decrease(q) =>
        val event = StockChanged(sku, -q)
        persist(event)(e => context.become(handler(quantity + e.quantity, booked)))
      case StockChanged(_, q) => {
        context.become(handler(quantity + q, booked))
      }
    }
}

2 个答案:

答案 0 :(得分:2)

我找到了答案。新的akka​​持久性2.6只能以功能方式起作用:)

https://doc.akka.io/docs/akka/current/typed/persistence.html#event-sourcing

答案 1 :(得分:2)

对于那些需要使用 Classic Persistence 的人,我能想到的最佳方法不需要演员范围的状态,但您仍然需要一个本地 var。然后,您可以将局部变量封装在恢复处理程序的闭包中。

扩展和清理问题中不完整的例子:

package item_example

import akka.actor.ActorLogging
import akka.persistence.PersistentActor

object ItemExample {

  case class Increase(quantity: Int)

  case class Decrease(quantity: Int)

  case class StockChanged(id: String, quantity: Int)

  class Item(sku: String) extends PersistentActor with ActorLogging {
    override def persistenceId: String = sku

    override def receiveCommand: Receive = listening()

    private def listening(quantity: Int = 0): Receive = {
      case Increase(q) =>
        persist(StockChanged(sku, q)) { e: StockChanged =>
          context.become(listening(quantity + e.quantity))
        }
      case Decrease(q) =>
        persist(StockChanged(sku, -q)) { e: StockChanged =>
          context.become(listening(quantity + e.quantity))
        }
    }

    override def receiveRecover: Receive = recovering()

    private def recovering(): Receive = {
      /*
      This is the local var that will be part of the recovery handler's closure.
      The semicolon is needed only so that the below code within parentheses is interpreted as
      a partial function and not a code block parameter for the integer 0.
      You can also just return a new PartialFunction[Any, Unit] but I find this to be nicer.
       */
      var totalQuantity: Int = 0;
      {
        case StockChanged(_, quantity) =>
          totalQuantity += quantity
          context.become(listening(totalQuantity))
      }
    }
  }
}

恢复处理程序将在每个恢复事件后覆盖状态。这是可以的,因为 recovery is guaranteed to finish before the actor starts consuming events from the mailbox

上述工作是因为在恢复处理程序中调用 context::become 不会修改恢复处理程序!它只修改邮箱处理程序。当我们浏览重放的事件时,我们只需在每个事件之后替换邮箱处理程序。 对于所有重播事件,恢复处理程序将被原封不动地调用。

旁注:区分发送给actor的命令和从持久存储重放的事件很重要。该问题定义了两个命令:IncreaseDecrease。它还定义了一个事件:StockChanged。但是,receiveCommand 方法处理所有三个。这是代码异味。 receiveCommand 应该处理命令,而 receiveRecover 应该处理事件。我已经在上面的解决方案中解决了这个问题。