Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala 阿克卡;使用EventSourcedBehavior传递太多参数_Scala_Akka_Event Sourcing - Fatal编程技术网

Scala 阿克卡;使用EventSourcedBehavior传递太多参数

Scala 阿克卡;使用EventSourcedBehavior传递太多参数,scala,akka,event-sourcing,Scala,Akka,Event Sourcing,我们正在构建一个基于Akka类型的事件源系统。我们很快就会陷入这样一种情况:我们的状态需要许多参数作为其参数传递 《样式指南》中有一个使用封闭类的解决方案 但是,这是针对“简单”行为的,而不是针对EventSourcedBehaviers。这种使用产生行为的封闭类的技术不适用于事件源行为,因为事件源行为由一个命令处理程序和一个事件处理程序组成 在我们的例子中,我们有一个状态特性,它定义了一个方法来接收命令,另一个方法来处理事件。如果我们应用封闭类技术,我们必须为所有状态创建匿名类,但这些状态不能

我们正在构建一个基于Akka类型的事件源系统。我们很快就会陷入这样一种情况:我们的状态需要许多参数作为其参数传递

《样式指南》中有一个使用封闭类的解决方案

但是,这是针对“简单”行为的,而不是针对EventSourcedBehaviers。这种使用产生行为的封闭类的技术不适用于事件源行为,因为事件源行为由一个命令处理程序和一个事件处理程序组成

在我们的例子中,我们有一个
状态
特性,它定义了一个方法来接收命令,另一个方法来处理事件。如果我们应用封闭类技术,我们必须为所有状态创建匿名类,但这些状态不能序列化

object SampleEventSourced {

  trait State extends CborSerializable {
    def execute(cmd: Command): ReplyEffect
    def apply(evt: Event): State
  }

  def apply(
    persistenceId: PersistenceId,
    config: Config,
  ): Behavior[Command] = {
    EventSourcedBehavior
      .withEnforcedReplies[Command, Event, State](
        persistenceId,
        new SampleEventSourced(config).empty(),
        (state, cmd) => state.execute(cmd),
        (state, evt) => state.apply(evt)
      )// ...
  }
}

class SampleEventSourced private(config: Config) {
  import SampleEventSourced._

  private def empty(): State = new State {
    override def execute(cmd: Command): ReplyEffect = cmd match {
      // ..
    }

    override def apply(evt: Event): State = evt match {
      // ..
    }
}
java.lang.IllegalArgumentException:空状态[org.acme.sampleEventSourceed$$anon$1@36d7d2fe]不可序列化

一种解决方案是在每个“状态”方法中复制事件源行为的创建。但这将产生大量重复

object SampleEventSourced {


  def apply(
    persistenceId: PersistenceId,
    config: Config,
  ): Behavior[Command] = new SampleEventSourced(config).empty()
}

class SampleEventSourced private(config: Config) {
  import SampleEventSourced._

  private def empty(): Behavior[Command] = EventSourcedBehavior
      .withEnforcedReplies[Command, Event, State](
        persistenceId,
        new State(),
        (state, cmd) => state.execute(cmd),
        (state, evt) => state.apply(evt)
      )// ...
  }
另一种方法是创建
State
的具体子类,但我们必须在所有这些状态之间传递参数

object SampleEventSourced {

  class EmptyState extends State(config:Config, otherUselessParameter:Any) {
    // ...
    override def apply(evt: Event): evt match {
      case _ => new OtherState(config, otherUselessParameter)
    }
  }
 
  class OtherState extends State(config:Config, veryImportantParameter:Any) {
    // ..
  }
}
将这些状态类放在封闭类中不会起作用,因为这些非静态内部类无法反序列化

object SampleEventSourced {

  trait State extends CborSerializable {
    def execute(cmd: Command): ReplyEffect
    def apply(evt: Event): State
  }

  def apply(
    persistenceId: PersistenceId,
    config: Config,
  ): Behavior[Command] = {
    EventSourcedBehavior
      .withEnforcedReplies[Command, Event, State](
        persistenceId,
        new SampleEventSourced(config).empty(),
        (state, cmd) => state.execute(cmd),
        (state, evt) => state.apply(evt)
      )// ...
  }
}

class SampleEventSourced private(config: Config) {
  import SampleEventSourced._

  private def empty(): State = new State {
    override def execute(cmd: Command): ReplyEffect = cmd match {
      // ..
    }

    override def apply(evt: Event): State = evt match {
      // ..
    }
}

那么,对于这种情况,您的解决方案是什么?您如何处理需要许多参数的状态的EventSourcedBehavior?

问题是,除了基于反射之外,
jackson cbor
,在以惯用的Scala方式序列化所做的事情时不是很有效(唯一对它有利的地方是它比Java序列化更好)

因此,显而易见的解决方案是:

  • 将状态建模为代数数据类型(通常是由
    案例类扩展的
    密封特征
    ,对于空/初始状态案例,可能是
    案例对象

  • 不要通过Jackson对它们进行序列化,而是使用Scala的本机序列化程序(例如play json、circe等);定义标记特征(例如
    CirceSerizable
    )可能更容易因此,在配置中,您只需担心一个binding.Events&state就可以用这个特性来标记:命令及其回复(至少可以通过网络发送的也可以)

定义序列化程序时需要注意的一个微妙之处是,Scala序列化程序的本机通常会在编译时使用某种形式的类型类派生,但如果需要序列化
ActorRef
,这将非常复杂,因为
ActorRef
的序列化程序最好借助
ActorSystem完成
EntityRef
现在可以检查序列化,并且在给定生命周期的情况下,从概念上讲对持久性更有意义,但这将是相当定制的):因此,您可能希望将
ActorRef
序列化器/反序列化器的派生延迟到
ActorSystem
启动,并将包含
ActorRef
的事件/状态/命令/回复的派生延迟到启动之后

我可能倾向于使每个事件的处理程序成为
sealed trait
中的一个方法,而不让状态知道事件是如何具体化的。这样做的好处是,您可以在不需要事件或命令的情况下对状态转换进行单元测试

case class ItemAdded(name: String) extends Event

sealed trait State {
  def items: Set[String]

  def addItem(name: String): State.NonEmpty
}

object State {
  case object Empty extends State {
    def items: Set[String] = Set.empty
    def addItem(name: String): NonEmpty = NonEmpty(Set(name))
  }

  case class NonEmpty(items: Set[String]) extends State {
    def addItem(name: String): NonEmpty = copy(items = items + name)
  }
}
那么您的事件处理程序就相当简单了

(state, evt) => {
  evt match {
    case ItemAdded(name) => state.addItem(name)
  }
}
根据您的命令处理程序进行的验证程度,这些验证可能会变得复杂。操作原理是,命令被解释为零个或多个事件,而事件被解释为状态上的操作(状态上的方法会导致新状态)。因此,状态应具有足够的查询方法(例如,
)以允许命令处理程序使用状态