上篇我们提到CQRS是一种读写分离式高并发、大流量数据录入体系,其中存写部分是通过event-sourcing+akka-persistence实现的。也可以这样理解:event-sourcing(事件源)是一种特殊数据录入模式,akka-persistence是这种模式的具体实现方式。事件源的核心思想是把某写发生的事件写入log(journal)。这些事件是能成功改变系统状态,并且时已经发生的事情。一开始我常常把事件源和命令源(command-sourcing)混为一谈。它们根本的区别事件event是已经发生的,命令command是待发生的。如果我们把命令存入journal,在对journal里的记录进行重新演算(replay)时就会执行命令并产生一些副作,如显示打印、发email等等。而系统状态和环境随着时间在不断变化,这些副作用也会在不同的时间产生不同的影响,这肯定是我们不想看见的。

事件源模式中,在内存里保存能代表程序状态的对象state-objects,这些状态对象与数据库表model之间建立了对应关系。假设程序中支持某些指令command,它们会改变程序的状态并且还可能还会产生一些副作用,那么用事件源做法的操作顺序应该是:产生副作用->存写事件->改变内存里的状态对象。其中任何一个环节失败都会放弃下面的环节。另一方面,在用journal中记录进行重新演算时,就需要先把发生的事件还原成改变状态的命令,人为的免去副作用,因为它已经在正确的时间产生过了,然后只要更新数据库model状态就算完成了。所以,实现persistence包括object和model之间对应、state-objects维护方式以及command和event之间的转换。

首先分析一下command与event之间的转换:我们还是用上一篇的POS收银系统做示范。下面是几个收银操作指令:

  1. case class GetItemInfo(itemcode: String) extends Command
  2. case class AddItem(item: Item, qty: Int) extends Command
  3. case class AliPay(amount: Double) extends Command

上面三个典型command可以下面的方式转换成event:

GetItemInfo:这是一个查询商品资料的指令,不影响交易状态,不必转换

AddItem: 这个指令只影响交易状态,没有副作用,转换成 :ItemAdded(item: Item, qty: Int) extends Event

AliPay:改变交易状态并且产生副作用,因为要即时从支付宝扣款。做法:先确定支付成功,然后转成: AliPaid(amount  Double) extends Event

  1. case class ItemAdded(item: Item, qty: Int) extends Event
  2. case class AliPaid(amount: Double) extends Event

POS收银交易状态是一张未结算账单内容,是个简单的交易记录清单SalesMemo:

  1. //交易记录
  2. case class TxnItem(
  3. num: Int //销售单号
  4. ,seq: Int //交易序号
  5. ,txntype: Int //交易类型编号
  6. ,code: String //编号(商品、账号...)
  7. ,qty: Int //交易数量
  8. ,price: Int //单价(分)
  9. ,amount: Int //金额(分)
  10. )
  11. case class SalesMemo(salesnum: Int, txnitems: List[TxnItem] = Nil) {
  12. def itemAdded(evt: Event): SalesMemo = evt match {
  13. case ItemAdded(item,qty) =>
  14. copy(txnitems = TxnItem(salesnum, txnitems.length+,,item.code,qty,item.price,qty * item.price) :: txnitems)
  15. case _ => this
  16. }
  17.  
  18. def aliPaid(evt: Event) = evt match {
  19. case AliPaid(amt) =>
  20. copy(txnitems = TxnItem(salesnum,txnitems.length+,,'ali',,amt,amt) :: items)
  21. case _ => this
  22. }
  23. }

itemAdded,aliPaid这两个函数分别代表AddItem和AliPay对状态对象的转变处理。

上面提到persistenceActor存写journal时对事件发生的顺序有严格要求,否则无法实现读取端正确恢复原始状态。这项要求的实现是通过persist/persistAsync这两种函数来实现的。下面是这几类函数的款式:

  1. //无干扰存写,后面进来的消息先存放在内部的临时存放点 message-stashing
  2. def persist[A](event: A)(handler: A Unit): Unit = {
  3. internalPersist(event)(handler)
  4. }
  5.  
  6. //同时存写多个事件
  7. def persistAll[A](events: immutable.Seq[A])(handler: A Unit): Unit = {
  8. internalPersistAll(events)(handler)
  9. }
  10.  
  11. //异步存写事件,没有临时存放点机制 no-message-stashing
  12. def persistAsync[A](event: A)(handler: A Unit): Unit = {
  13. internalPersistAsync(event)(handler)
  14. }
  15.  
  16. //异步存写多项事件
  17. def persistAllAsync[A](events: immutable.Seq[A])(handler: A Unit): Unit = {
  18. internalPersistAllAsync(events)(handler)
  19. }
  20.  
  21. //不存写事件,利用内部临时存放点机制来保证handler执行顺序
  22. def defer[A](event: A)(handler: A Unit): Unit = {
  23. internalDefer(event)(handler)
  24. }
  25.  
  26. //不存写事件,只保证handler运行顺序
  27. def deferAsync[A](event: A)(handler: A Unit): Unit = {
  28. internalDeferAsync(event)(handler)
  29. }

无论如何,handler函数都保证在事件存写动作成功后才能运行。我们用一些伪代码来示范有临存stash及无临存no-stash时handler运行的顺序:

  1. override def receiveCommand: Receive = {
  2. case c: String {
  3. sender() ! c
  4. persist(s"evt-$c-1") { e sender() ! e }
  5. persist(s"evt-$c-2") { e sender() ! e }
  6. defer(s"evt-$c-3") { e sender() ! e }
  7. }
  8. }
  9.  
  10. //有内部临存 with message stashing
  11. persistentActor ! "a"
  12. persistentActor ! "b"
  13.  
  14. // order of received messages:
  15. // a
  16. // evt-a-1
  17. // evt-a-2
  18. // evt-a-3
  19. // b
  20. // evt-b-1
  21. // evt-b-2
  22. // evt-b-3
  23.  
  24. ----------------------------------
  25. override def receiveCommand: Receive = {
  26. case c: String {
  27. sender() ! c
  28. persistAsync(s"evt-$c-1") { e sender() ! e }
  29. persistAsync(s"evt-$c-2") { e sender() ! e }
  30. deferAsync(s"evt-$c-3") { e sender() ! e }
  31. }
  32. }
  33.  
  34. persistentActor ! "a"
  35. persistentActor ! "b"
  36.  
  37. // order of received messages:
  38. // a
  39. // b //无临存机制,外部信息立即处理了
  40. // evt-a-1
  41. // evt-a-2
  42. // evt-a-3
  43. // evt-b-1
  44. // evt-b-2
  45. // evt-b-3

如果发生内嵌多层persist时,正确的顺序如下:

  1. override def receiveCommand: Receive = {
  2. case c: String
  3. sender() ! c
  4.  
  5. persist(s"$c-1-outer") { outer1
  6. sender() ! outer1
  7. persist(s"$c-1-inner") { inner1
  8. sender() ! inner1
  9. }
  10. }
  11.  
  12. persist(s"$c-2-outer") { outer2
  13. sender() ! outer2
  14. persist(s"$c-2-inner") { inner2
  15. sender() ! inner2
  16. }
  17. }
  18. }
  19.  
  20. persistentActor ! "a"
  21. persistentActor ! "b"
  22.  
  23. // order of received messages:
  24. // a
  25. // a-outer-1
  26. // a-outer-2
  27. // a-inner-1
  28. // a-inner-2
  29. // and only then process "b"
  30. // b
  31. // b-outer-1
  32. // b-outer-2
  33. // b-inner-1
  34. // b-inner-2
  35.  
  36. --------------------------------
  37. override def receiveCommand: Receive = {
  38. case c: String
  39. sender() ! c
  40. persistAsync(c + "-outer-1") { outer
  41. sender() ! outer
  42. persistAsync(c + "-inner-1") { inner sender() ! inner }
  43. }
  44. persistAsync(c + "-outer-2") { outer
  45. sender() ! outer
  46. persistAsync(c + "-inner-2") { inner sender() ! inner }
  47. }
  48. }
  49.  
  50. persistentActor ! "a"
  51. persistentActor ! "b"
  52.  
  53. // order of received messages:
  54. // a
  55. // b
  56. // a-outer-1
  57. // a-outer-2
  58. // b-outer-1
  59. // b-outer-2
  60. // a-inner-1
  61. // a-inner-2
  62. // b-inner-1
  63. // b-inner-2
  64.  
  65. // which can be seen as the following causal relationship:
  66. // a -> a-outer-1 -> a-outer-2 -> a-inner-1 -> a-inner-2
  67. // b -> b-outer-1 -> b-outer-2 -> b-inner-1 -> b-inner-2

值得注意的是这个handler函数只会在事件存写成功后才运行,失败则否。也就是说确认了事件已经安全存写后才更新state-objects状态(model状态在CQRS读取时再相应更新)。针对上面的POS例子里可以用下面的代码处理方式:

  1. override def receiveCommand: Receive = {
  2. case AddItem(item,qty) =>
  3. persist(ItemAdded(item,qty))(salesMemo.itemAdded)
  4. case AliPay(amt) =>
  5. try {
  6. if (aliOnlinePay(amt)) //先产生副作用
  7. persist(AliPaid(amt))(salesMemo.alipaid(_))
  8. } catch {
  9. case _ > Throw new OnlinePayExecption("boom!!!")
  10. }
  11. ...

akka-persistence代表CQRS模式中以事件源方式存写数据的具体实现。我们提到过,数据存写具体做法是向一个journal里写入发生的改变状态目标state-objects的事件。每次PersistenceActor启动时都会从journal里读取之前写入的事件、还原成指令command、然后逐步把state-objects恢复到上次停止时的状态,不管是因异常还是正常停止的。这个恢复状态的过程是由PersistenceActor的receiveRecovery函数实现的,如下:

  1. override def receiveRecover: Receive = {
  2. case evt: Event =>
  3. salesMemo = salesMemo.updateMemo(evt)
  4. case SnapshotOffer(_,loggedItems: SalesMemo) =>
  5. salesMemo = loggedItems
  6. }

按理来说恢复状态即是把事件从头到尾再演算一遍。不过这种方式效率是个大问题,试想每次启动都需要先读取几十万条数据会是怎样的感受。效率问题的解决方法就是通过存写快照方式把之前的事件总结成快照snapshot形式的阶段状态,然后存入快照库(snapshot-store)。这样在PersistenceActor启动时先用最后一个快照把状态恢复到一个阶段,然后再读取快照产生之后的所有事件对阶段性状态再转换成最新状态。快照的读写函数如下:

  1. def saveSnapshot(snapshot: Any): Unit = {
  2. snapshotStore ! SaveSnapshot(SnapshotMetadata(snapshotterId, snapshotSequenceNr), snapshot)
  3. }
  4.  
  5. /**
  6. * Offers a [[PersistentActor]] a previously saved `snapshot` during recovery. This offer is received
  7. * before any further replayed messages.
  8. */
  9. @SerialVersionUID(1L)
  10. final case class SnapshotOffer(metadata: SnapshotMetadata, snapshot: Any)
  11.  
  12. /**
  13. * Snapshot metadata.
  14. *
  15. * @param persistenceId id of persistent actor from which the snapshot was taken.
  16. * @param sequenceNr sequence number at which the snapshot was taken.
  17. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown.
  18. */
  19. @SerialVersionUID(1L) //#snapshot-metadata
  20. final case class SnapshotMetadata(persistenceId: String, sequenceNr: Long, timestamp: Long = 0L)
  21. //#snapshot-metadata

PersistenceActor里有receiveRecover和receiveCommand两个抽象函数,必须由用户提供具体的实现。这两个函数代表了PersistentActor的两大功能:状态复原和消息处理。状态复原是通过receiveRecover对snapshot-store和journal里的记录处理实现的。而PersistentActor的receiveCommand就是普通Actor的receive消息处理函数。用户可以通过PersistentActor提供的回调(callback)函数来进行事件读取过程前的事前准备和后面的事后处理。可以对这些callback函数进行重载(override)来自定义这些处理程序,如:

  1. /**
  2. * Called whenever a message replay fails. By default it logs the error.
  3. *
  4. * Subclass may override to customize logging.
  5. *
  6. * The actor is always stopped after this method has been invoked.
  7. *
  8. * @param cause failure cause.
  9. * @param event the event that was processed in `receiveRecover`, if the exception
  10. * was thrown there
  11. */
  12. protected def onRecoveryFailure(cause: Throwable, event: Option[Any]): Unit =
  13. ...
  14. /**
  15. * Called when persist fails. By default it logs the error.
  16. * Subclass may override to customize logging and for example send negative
  17. * acknowledgment to sender.
  18. *
  19. * The actor is always stopped after this method has been invoked.
  20. *
  21. * Note that the event may or may not have been saved, depending on the type of
  22. * failure.
  23. *
  24. * @param cause failure cause.
  25. * @param event the event that was to be persisted
  26. */
  27. protected def onPersistFailure(cause: Throwable, event: Any, seqNr: Long): Unit = {
  28. ...
  29. /**
  30. * Called when the journal rejected `persist` of an event. The event was not
  31. * stored. By default this method logs the problem as a warning, and the actor continues.
  32. * The callback handler that was passed to the `persist` method will not be invoked.
  33. *
  34. * @param cause failure cause
  35. * @param event the event that was to be persisted
  36. */
  37. protected def onPersistRejected(cause: Throwable, event: Any, seqNr: Long): Unit = {
  38. ...

也可以通过函数重载来自定义状态恢复行为:

  1. trait PersistenceRecovery {
  2. //#persistence-recovery
  3. /**
  4. * Called when the persistent actor is started for the first time.
  5. * The returned [[Recovery]] object defines how the Actor will recover its persistent state before
  6. * handling the first incoming message.
  7. *
  8. * To skip recovery completely return `Recovery.none`.
  9. */
  10. def recovery: Recovery = Recovery()
  11. //#persistence-recovery
  12. }

整个状态恢复过程是在EventSourced.scala里下面这个函数实现的:

  1. override def stateReceive(receive: Receive, message: Any) = try message match {
  2. case ReplayedMessage(p)
  3. try {
  4. eventSeenInInterval = true
  5. updateLastSequenceNr(p)
  6. Eventsourced.super.aroundReceive(recoveryBehavior, p)
  7. } catch {
  8. case NonFatal(t)
  9. timeoutCancellable.cancel()
  10. try onRecoveryFailure(t, Some(p.payload)) finally context.stop(self)
  11. returnRecoveryPermit()
  12. }
  13. case RecoverySuccess(highestSeqNr)
  14. timeoutCancellable.cancel()
  15. onReplaySuccess() // callback for subclass implementation
  16. sequenceNr = highestSeqNr
  17. setLastSequenceNr(highestSeqNr)
  18. _recoveryRunning = false
  19. try Eventsourced.super.aroundReceive(recoveryBehavior, RecoveryCompleted)
  20. finally transitToProcessingState()
  21. case ReplayMessagesFailure(cause)
  22. timeoutCancellable.cancel()
  23. try onRecoveryFailure(cause, event = None) finally context.stop(self)
  24. case RecoveryTick(false) if !eventSeenInInterval
  25. timeoutCancellable.cancel()
  26. try onRecoveryFailure(
  27. new RecoveryTimedOut(s"Recovery timed out, didn't get event within $timeout, highest sequence number seen $lastSequenceNr"),
  28. event = None)
  29. finally context.stop(self)
  30. case RecoveryTick(false)
  31. eventSeenInInterval = false
  32. case RecoveryTick(true)
  33. // snapshot tick, ignore
  34. case other
  35. stashInternally(other)
  36. } catch {
  37. case NonFatal(e)
  38. returnRecoveryPermit()
  39. throw e
  40. }

函数通过super.aroundReceive把消息传给了receiveRecovery:

  1. /**
  2. * INTERNAL API.
  3. *
  4. * Can be overridden to intercept calls to this actor's current behavior.
  5. *
  6. * @param receive current behavior.
  7. * @param msg current message.
  8. */
  9. protected[akka] def aroundReceive(receive: Actor.Receive, msg: Any): Unit = {
  10. // optimization: avoid allocation of lambda
  11. if (receive.applyOrElse(msg, Actor.notHandledFun).asInstanceOf[AnyRef] eq Actor.NotHandled) {
  12. unhandled(msg)
  13. }
  14. }

因为EventSourced继承了PersistenceRecovery trait,所以重载recovery函数可以改变状态恢复行为。默认的模式是:

  1. /**
  2. * Recovery mode configuration object to be returned in [[PersistentActor#recovery]].
  3. *
  4. * By default recovers from latest snapshot replays through to the last available event (last sequenceId).
  5. *
  6. * Recovery will start from a snapshot if the persistent actor has previously saved one or more snapshots
  7. * and at least one of these snapshots matches the specified `fromSnapshot` criteria.
  8. * Otherwise, recovery will start from scratch by replaying all stored events.
  9. *
  10. * If recovery starts from a snapshot, the persistent actor is offered that snapshot with a [[SnapshotOffer]]
  11. * message, followed by replayed messages, if any, that are younger than the snapshot, up to the
  12. * specified upper sequence number bound (`toSequenceNr`).
  13. *
  14. * @param fromSnapshot criteria for selecting a saved snapshot from which recovery should start. Default
  15. * is latest (= youngest) snapshot.
  16. * @param toSequenceNr upper sequence number bound (inclusive) for recovery. Default is no upper bound.
  17. * @param replayMax maximum number of messages to replay. Default is no limit.
  18. */
  19. @SerialVersionUID(1L)
  20. final case class Recovery(
  21. fromSnapshot: SnapshotSelectionCriteria = SnapshotSelectionCriteria.Latest,
  22. toSequenceNr: Long = Long.MaxValue,
  23. replayMax: Long = Long.MaxValue)

下面是状态恢复过程中产生的消息:

  1. /**
  2. * Sent to a [[PersistentActor]] when the journal replay has been finished.
  3. */
  4. @SerialVersionUID(1L)
  5. case object RecoveryCompleted extends RecoveryCompleted {
  6. ...
  7. final class RecoveryTimedOut(message: String) extends RuntimeException(message) with NoStackTrace

状态恢复的进程可用用下面的方法检测:

  1. /**
  2. * Returns `true` if this persistent actor is currently recovering.
  3. */
  4. def recoveryRunning: Boolean = {
  5. // currentState is null if this is called from constructor
  6. if (currentState == null) true else currentState.recoveryRunning
  7. }
  8.  
  9. /**
  10. * Returns `true` if this persistent actor has successfully finished recovery.
  11. */
  12. def recoveryFinished: Boolean = !recoveryRunning

用户也可以删除journal里的事件。虽然应该作为原始资料完整保存不应该鼓励这么做:

  1. /**
  2. * Permanently deletes all persistent messages with sequence numbers less than or equal `toSequenceNr`.
  3. *
  4. * If the delete is successful a [[DeleteMessagesSuccess]] will be sent to the actor.
  5. * If the delete fails a [[DeleteMessagesFailure]] will be sent to the actor.
  6. *
  7. * @param toSequenceNr upper sequence number bound of persistent messages to be deleted.
  8. */
  9. def deleteMessages(toSequenceNr: Long): Unit =
  10. journal ! DeleteMessagesTo(persistenceId, toSequenceNr, self)

删除事件范围是用SequenceNr来代表的,下面是一些可用的序号:

  1. /**
  2. * Returns `persistenceId`.
  3. */
  4. override def snapshotterId: String = persistenceId
  5.  
  6. /**
  7. * Highest received sequence number so far or `0L` if this actor hasn't replayed
  8. * or stored any persistent events yet.
  9. */
  10. def lastSequenceNr: Long = _lastSequenceNr
  11.  
  12. /**
  13. * Returns `lastSequenceNr`.
  14. */
  15. def snapshotSequenceNr: Long = lastSequenceNr

事件删除过程可用下面的消息监控:

  1. /**
  2. * Reply message to a successful [[Eventsourced#deleteMessages]] request.
  3. */
  4. final case class DeleteMessagesSuccess(toSequenceNr: Long)
  5.  
  6. /**
  7. * Reply message to a failed [[Eventsourced#deleteMessages]] request.
  8. */
  9. final case class DeleteMessagesFailure(cause: Throwable, toSequenceNr: Long)

下面是一些快照的持久化维护方法:

  1. /**
  2. * Snapshotter id.
  3. */
  4. def snapshotterId: String
  5.  
  6. /**
  7. * Sequence number to use when taking a snapshot.
  8. */
  9. def snapshotSequenceNr: Long
  10.  
  11. /**
  12. * Instructs the snapshot store to load the specified snapshot and send it via an [[SnapshotOffer]]
  13. * to the running [[PersistentActor]].
  14. */
  15. def loadSnapshot(persistenceId: String, criteria: SnapshotSelectionCriteria, toSequenceNr: Long) =
  16. snapshotStore ! LoadSnapshot(persistenceId, criteria, toSequenceNr)
  17.  
  18. /**
  19. * Saves a `snapshot` of this snapshotter's state.
  20. *
  21. * The [[PersistentActor]] will be notified about the success or failure of this
  22. * via an [[SaveSnapshotSuccess]] or [[SaveSnapshotFailure]] message.
  23. */
  24. def saveSnapshot(snapshot: Any): Unit = {
  25. snapshotStore ! SaveSnapshot(SnapshotMetadata(snapshotterId, snapshotSequenceNr), snapshot)
  26. }
  27.  
  28. /**
  29. * Deletes the snapshot identified by `sequenceNr`.
  30. *
  31. * The [[PersistentActor]] will be notified about the status of the deletion
  32. * via an [[DeleteSnapshotSuccess]] or [[DeleteSnapshotFailure]] message.
  33. */
  34. def deleteSnapshot(sequenceNr: Long): Unit = {
  35. snapshotStore ! DeleteSnapshot(SnapshotMetadata(snapshotterId, sequenceNr))
  36. }
  37.  
  38. /**
  39. * Deletes all snapshots matching `criteria`.
  40. *
  41. * The [[PersistentActor]] will be notified about the status of the deletion
  42. * via an [[DeleteSnapshotsSuccess]] or [[DeleteSnapshotsFailure]] message.
  43. */
  44. def deleteSnapshots(criteria: SnapshotSelectionCriteria): Unit = {
  45. snapshotStore ! DeleteSnapshots(snapshotterId, criteria)
  46. }

快照维护数据结构和消息如下:

  1. /**
  2. * Snapshot metadata.
  3. *
  4. * @param persistenceId id of persistent actor from which the snapshot was taken.
  5. * @param sequenceNr sequence number at which the snapshot was taken.
  6. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown.
  7. */
  8. @SerialVersionUID(1L) //#snapshot-metadata
  9. final case class SnapshotMetadata(persistenceId: String, sequenceNr: Long, timestamp: Long = 0L)
  10. //#snapshot-metadata
  11.  
  12. object SnapshotMetadata {
  13. implicit val ordering: Ordering[SnapshotMetadata] = Ordering.fromLessThan[SnapshotMetadata] { (a, b)
  14. if (a eq b) false
  15. else if (a.persistenceId != b.persistenceId) a.persistenceId.compareTo(b.persistenceId) <
  16. else if (a.sequenceNr != b.sequenceNr) a.sequenceNr < b.sequenceNr
  17. else if (a.timestamp != b.timestamp) a.timestamp < b.timestamp
  18. else false
  19. }
  20. }
  21.  
  22. /**
  23. * Sent to a [[PersistentActor]] after successful saving of a snapshot.
  24. *
  25. * @param metadata snapshot metadata.
  26. */
  27. @SerialVersionUID(1L)
  28. final case class SaveSnapshotSuccess(metadata: SnapshotMetadata)
  29. extends SnapshotProtocol.Response
  30.  
  31. /**
  32. * Sent to a [[PersistentActor]] after successful deletion of a snapshot.
  33. *
  34. * @param metadata snapshot metadata.
  35. */
  36. @SerialVersionUID(1L)
  37. final case class DeleteSnapshotSuccess(metadata: SnapshotMetadata)
  38. extends SnapshotProtocol.Response
  39.  
  40. /**
  41. * Sent to a [[PersistentActor]] after successful deletion of specified range of snapshots.
  42. *
  43. * @param criteria snapshot selection criteria.
  44. */
  45. @SerialVersionUID(1L)
  46. final case class DeleteSnapshotsSuccess(criteria: SnapshotSelectionCriteria)
  47. extends SnapshotProtocol.Response
  48.  
  49. /**
  50. * Sent to a [[PersistentActor]] after failed saving of a snapshot.
  51. *
  52. * @param metadata snapshot metadata.
  53. * @param cause failure cause.
  54. */
  55. @SerialVersionUID(1L)
  56. final case class SaveSnapshotFailure(metadata: SnapshotMetadata, cause: Throwable)
  57. extends SnapshotProtocol.Response
  58.  
  59. /**
  60. * Sent to a [[PersistentActor]] after failed deletion of a snapshot.
  61. *
  62. * @param metadata snapshot metadata.
  63. * @param cause failure cause.
  64. */
  65. @SerialVersionUID(1L)
  66. final case class DeleteSnapshotFailure(metadata: SnapshotMetadata, cause: Throwable)
  67. extends SnapshotProtocol.Response
  68.  
  69. /**
  70. * Sent to a [[PersistentActor]] after failed deletion of a range of snapshots.
  71. *
  72. * @param criteria snapshot selection criteria.
  73. * @param cause failure cause.
  74. */
  75. @SerialVersionUID(1L)
  76. final case class DeleteSnapshotsFailure(criteria: SnapshotSelectionCriteria, cause: Throwable)
  77. extends SnapshotProtocol.Response
  78.  
  79. /**
  80. * Offers a [[PersistentActor]] a previously saved `snapshot` during recovery. This offer is received
  81. * before any further replayed messages.
  82. */
  83. @SerialVersionUID(1L)
  84. final case class SnapshotOffer(metadata: SnapshotMetadata, snapshot: Any)
  85.  
  86. /**
  87. * Selection criteria for loading and deleting snapshots.
  88. *
  89. * @param maxSequenceNr upper bound for a selected snapshot's sequence number. Default is no upper bound,
  90. * i.e. `Long.MaxValue`
  91. * @param maxTimestamp upper bound for a selected snapshot's timestamp. Default is no upper bound,
  92. * i.e. `Long.MaxValue`
  93. * @param minSequenceNr lower bound for a selected snapshot's sequence number. Default is no lower bound,
  94. * i.e. `0L`
  95. * @param minTimestamp lower bound for a selected snapshot's timestamp. Default is no lower bound,
  96. * i.e. `0L`
  97. *
  98. * @see [[Recovery]]
  99. */
  100. @SerialVersionUID(1L)
  101. final case class SnapshotSelectionCriteria(
  102. maxSequenceNr: Long = Long.MaxValue,
  103. maxTimestamp: Long = Long.MaxValue,
  104. minSequenceNr: Long = 0L,
  105. minTimestamp: Long = 0L) {
  106.  
  107. /**
  108. * INTERNAL API.
  109. */
  110. private[persistence] def limit(toSequenceNr: Long): SnapshotSelectionCriteria =
  111. if (toSequenceNr < maxSequenceNr) copy(maxSequenceNr = toSequenceNr) else this
  112.  
  113. /**
  114. * INTERNAL API.
  115. */
  116. private[persistence] def matches(metadata: SnapshotMetadata): Boolean =
  117. metadata.sequenceNr <= maxSequenceNr && metadata.timestamp <= maxTimestamp &&
  118. metadata.sequenceNr >= minSequenceNr && metadata.timestamp >= minTimestamp
  119. }
  120.  
  121. object SnapshotSelectionCriteria {
  122. /**
  123. * The latest saved snapshot.
  124. */
  125. val Latest = SnapshotSelectionCriteria()
  126.  
  127. /**
  128. * No saved snapshot matches.
  129. */
  130. val None = SnapshotSelectionCriteria(0L, 0L)
  131.  
  132. /**
  133. * Java API.
  134. */
  135. def create(maxSequenceNr: Long, maxTimestamp: Long) =
  136. SnapshotSelectionCriteria(maxSequenceNr, maxTimestamp)
  137.  
  138. /**
  139. * Java API.
  140. */
  141. def create(maxSequenceNr: Long, maxTimestamp: Long,
  142. minSequenceNr: Long, minTimestamp: Long) =
  143. SnapshotSelectionCriteria(maxSequenceNr, maxTimestamp, minSequenceNr, minTimestamp)
  144.  
  145. /**
  146. * Java API.
  147. */
  148. def latest() = Latest
  149.  
  150. /**
  151. * Java API.
  152. */
  153. def none() = None
  154. }
  155.  
  156. /**
  157. * Plugin API: a selected snapshot matching [[SnapshotSelectionCriteria]].
  158. *
  159. * @param metadata snapshot metadata.
  160. * @param snapshot snapshot.
  161. */
  162. final case class SelectedSnapshot(metadata: SnapshotMetadata, snapshot: Any)
  163.  
  164. object SelectedSnapshot {
  165. /**
  166. * Java API, Plugin API.
  167. */
  168. def create(metadata: SnapshotMetadata, snapshot: Any): SelectedSnapshot =
  169. SelectedSnapshot(metadata, snapshot)
  170. }
  171.  
  172. /**
  173. * INTERNAL API.
  174. *
  175. * Defines messages exchanged between persistent actors and a snapshot store.
  176. */
  177. private[persistence] object SnapshotProtocol {
  178.  
  179. /** Marker trait shared by internal snapshot messages. */
  180. sealed trait Message extends Protocol.Message
  181. /** Internal snapshot command. */
  182. sealed trait Request extends Message
  183. /** Internal snapshot acknowledgement. */
  184. sealed trait Response extends Message
  185.  
  186. /**
  187. * Instructs a snapshot store to load a snapshot.
  188. *
  189. * @param persistenceId persistent actor id.
  190. * @param criteria criteria for selecting a snapshot from which recovery should start.
  191. * @param toSequenceNr upper sequence number bound (inclusive) for recovery.
  192. */
  193. final case class LoadSnapshot(persistenceId: String, criteria: SnapshotSelectionCriteria, toSequenceNr: Long)
  194. extends Request
  195.  
  196. /**
  197. * Response message to a [[LoadSnapshot]] message.
  198. *
  199. * @param snapshot loaded snapshot, if any.
  200. */
  201. final case class LoadSnapshotResult(snapshot: Option[SelectedSnapshot], toSequenceNr: Long)
  202. extends Response
  203.  
  204. /**
  205. * Reply message to a failed [[LoadSnapshot]] request.
  206. * @param cause failure cause.
  207. */
  208. final case class LoadSnapshotFailed(cause: Throwable) extends Response
  209.  
  210. /**
  211. * Instructs snapshot store to save a snapshot.
  212. *
  213. * @param metadata snapshot metadata.
  214. * @param snapshot snapshot.
  215. */
  216. final case class SaveSnapshot(metadata: SnapshotMetadata, snapshot: Any)
  217. extends Request
  218.  
  219. /**
  220. * Instructs snapshot store to delete a snapshot.
  221. *
  222. * @param metadata snapshot metadata.
  223. */
  224. final case class DeleteSnapshot(metadata: SnapshotMetadata)
  225. extends Request
  226.  
  227. /**
  228. * Instructs snapshot store to delete all snapshots that match `criteria`.
  229. *
  230. * @param persistenceId persistent actor id.
  231. * @param criteria criteria for selecting snapshots to be deleted.
  232. */
  233. final case class DeleteSnapshots(persistenceId: String, criteria: SnapshotSelectionCriteria)
  234. extends Request
  235. }

篇幅所限,我们将在下一篇用一个具体的应用例子来进行akka-CQRS写端示范。

Akka-CQRS(1)- Write-side, Persisting event sources:CQRS存写端操作方式的更多相关文章

  1. Command and Query Responsibility Segregation (CQRS) Pattern 命令和查询职责分离(CQRS)模式

    Segregate operations that read data from operations that update data by using separate interfaces. T ...

  2. CQRS\ES架构介绍

    大家好,我叫汤雪华.我平时工作使用Java,业余时间喜欢用C#做点开源项目,如ENode, EQueue.我个人对DDD领域驱动设计.CQRS架构.事件溯源(Event Sourcing,简称ES). ...

  3. Akka-CQRS(6)- read-side

    前面我们全面介绍了在akka-cluster环境下实现的CQRS写端write-side.简单来说就是把发生事件描述作为对象严格按发生时间顺序写入数据库.这些事件对象一般是按照二进制binary方式如 ...

  4. NIO-EPollSelectorIpml源码分析

    目录 NIO-EPollSelectorIpml源码分析 目录 前言 初始化EPollSelectorProvider 创建EPollSelectorImpl EPollSelectorImpl结构 ...

  5. Akka系列(八):Akka persistence设计理念之CQRS

    前言........ 这一篇文章主要是讲解Akka persistence的核心设计理念,也是CQRS(Command Query Responsibility Segregation)架构设计的典型 ...

  6. CQRS与Event Sourcing之浅见

    引言 DDD是近年软件设计的热门.CQRS与Event Sourcing作为实施DDD的一种选择,也逐步进入人们的视野.围绕这两个主题,软件开发的大咖[Martin Fowler].[Greg You ...

  7. CQRS, Task Based UIs, Event Sourcing agh!

    原文地址:CQRS, Task Based UIs, Event Sourcing agh! Many people have been getting confused over what CQRS ...

  8. CQRS Event Sourcing介绍

    什么是CQRS模式? CQRS是Command and Query Responsibility Segregation的缩写,直译就是命令与查询责任分离的意思. 命令会改变对象的状态,但不返回任何数 ...

  9. CQRS FAQ (翻译)

    我从接触ddd到学习cqrs有6年多了, 其中也遇到了不少疑问, 也向很多的前辈牛人请教得到了很多宝贵的意见和建议. 偶尔的机会看到国外有个站点专门罗列了ddd, cqrs和事件溯源的常见问题. 其中 ...

随机推荐

  1. NET CORE 2.0发布在IIS上提示502.5错误

    在装了WindowsHosting和NET CORE SDK后,如果没有重启服务器,访问站点报以上错误,解决办法要么重启,要么执行以下两条命令: net stop was /y net start w ...

  2. echarts 图表重新加载,原来的数据依然存在图表上

    问题 在做一个全国地图上一些饼图,并且向省一级的地图钻取的时候,原来的饼图依然显示 原因 echars所有添加的图表都在一个series属性集合中,并且同一个echars对象默认是合并之前的数据的,所 ...

  3. 微信小程序上拉下拉刷新

    小程序提供了,onPullDownRefresh和onReachBottom两个事件函数监听下拉和上拉事件函数.提示加载中,取消加载中 效果: js文件 // pages/enterprise/ent ...

  4. ElasticSearch聚合(转)

    ES之五:ElasticSearch聚合 前言 说完了ES的索引与检索,接着再介绍一个ES高级功能API – 聚合(Aggregations),聚合功能为ES注入了统计分析的血统,使用户在面对大数据提 ...

  5. centos 上docker 运行出现/bin/sh: . not found

    本人用的是Linux上的编译的....然后用了win上的编译命令 解决办法 : 用Linux上的编译命令.....    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 g ...

  6. linux 图形化界面 && 谷歌浏览器 安装

    一.图形化界面安装 yum groupinstall "Desktop" 如果运行显示 则 yum groupinstall "X Window System" ...

  7. Sql Server免域,异地备份

    --先决条件 开启xp_cmdshellEXEC sp_configure 'show advanced options', 1;RECONFIGURE;EXEC sp_configure 'xp_c ...

  8. JavaScript判断系统语言

    var lang= null; function language(){ var language=null; if (navigator.appName == 'Netscape'){ langua ...

  9. (PMP)解题技巧和典型题目分析(0903-2班)

    1.计算题 ,5 2.概念题,少 3.情景题,很多 C B C D ------------------------------------------------------------------ ...

  10. JAVA 8 主要新特性 ----------------(六)集合Stream API

    一.简介Stream Java8中有两大最为重要的改变.第一个是 Lambda 表达式:另外一 个则是 Stream API(java.util.stream.*).Stream 是 Java8 中处 ...