我一直在尝试使用功能性方法来实现持久性参与者-我的意思是根本没有任何变量。但我遇到麻烦了:)下面的代码示例无法正常工作,因为处理程序参数未在处理程序之间共享(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))
}
}
}
答案 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的命令和从持久存储重放的事件很重要。该问题定义了两个命令:Increase
和 Decrease
。它还定义了一个事件:StockChanged
。但是,receiveCommand
方法处理所有三个。这是代码异味。 receiveCommand
应该处理命令,而 receiveRecover
应该处理事件。我已经在上面的解决方案中解决了这个问题。