=per #3915 Make become work during recovery for EventsourcedProcessor et.c.

This commit is contained in:
Björn Antonsson 2014-03-19 11:59:16 +01:00
parent 82ee7e2ede
commit 26c493ea4a
21 changed files with 136 additions and 86 deletions

View file

@ -74,7 +74,7 @@ Processors
A processor can be implemented by extending ``AbstractProcessor`` class and implementing the A processor can be implemented by extending ``AbstractProcessor`` class and implementing the
``receive`` method. ``receive`` method.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#definition .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#definition
Processors only write messages of type ``Persistent`` to the journal, others are received without being persisted. Processors only write messages of type ``Persistent`` to the journal, others are received without being persisted.
When a processor's ``receive`` method is called with a ``Persistent`` message it can safely assume that this message When a processor's ``receive`` method is called with a ``Persistent`` message it can safely assume that this message
@ -85,7 +85,7 @@ so that the sender can re-send the message, if needed.
An ``AbstractProcessor`` itself is an ``Actor`` and can therefore be instantiated with ``actorOf``. An ``AbstractProcessor`` itself is an ``Actor`` and can therefore be instantiated with ``actorOf``.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#usage .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#usage
Recovery Recovery
-------- --------
@ -99,28 +99,28 @@ Recovery customization
Automated recovery on start can be disabled by overriding ``preStart`` with an empty implementation. Automated recovery on start can be disabled by overriding ``preStart`` with an empty implementation.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-disabled .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-disabled
In this case, a processor must be recovered explicitly by sending it a ``Recover`` message. In this case, a processor must be recovered explicitly by sending it a ``Recover`` message.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#recover-explicit .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-explicit
If not overridden, ``preStart`` sends a ``Recover`` message to ``self()``. Applications may also override If not overridden, ``preStart`` sends a ``Recover`` message to ``self()``. Applications may also override
``preStart`` to define further ``Recover`` parameters such as an upper sequence number bound, for example. ``preStart`` to define further ``Recover`` parameters such as an upper sequence number bound, for example.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-custom .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-custom
Upper sequence number bounds can be used to recover a processor to past state instead of current state. Automated Upper sequence number bounds can be used to recover a processor to past state instead of current state. Automated
recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation. recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-restart-disabled .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-restart-disabled
Recovery status Recovery status
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
A processor can query its own recovery status via the methods A processor can query its own recovery status via the methods
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#recovery-status .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recovery-status
.. _failure-handling-java-lambda: .. _failure-handling-java-lambda:
@ -130,7 +130,7 @@ Failure handling
A persistent message that caused an exception will be received again by a processor after restart. To prevent A persistent message that caused an exception will be received again by a processor after restart. To prevent
a replay of that message during recovery it can be deleted. a replay of that message during recovery it can be deleted.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#deletion .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#deletion
Message deletion Message deletion
---------------- ----------------
@ -149,13 +149,13 @@ A processor must have an identifier that doesn't change across different actor i
``String`` representation of processor's path without the address part and can be obtained via the ``processorId`` ``String`` representation of processor's path without the address part and can be obtained via the ``processorId``
method. method.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#processor-id .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#processor-id
Applications can customize a processor's id by specifying an actor name during processor creation as shown in Applications can customize a processor's id by specifying an actor name during processor creation as shown in
section :ref:`processors-java`. This changes that processor's name in its actor hierarchy and hence influences only section :ref:`processors-java`. This changes that processor's name in its actor hierarchy and hence influences only
part of the processor id. To fully customize a processor's id, the ``processorId`` method must be overridden. part of the processor id. To fully customize a processor's id, the ``processorId`` method must be overridden.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#processor-id-override .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#processor-id-override
Overriding ``processorId`` is the recommended way to generate stable identifiers. Overriding ``processorId`` is the recommended way to generate stable identifiers.
@ -168,7 +168,7 @@ Views can be implemented by extending the ``AbstractView`` abstract class and im
``processorId`` ``processorId``
methods. methods.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#view .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#view
The ``processorId`` identifies the processor from which the view receives journaled messages. It is not necessary The ``processorId`` identifies the processor from which the view receives journaled messages. It is not necessary
the referenced processor is actually running. Views read messages from a processor's journal directly. When a the referenced processor is actually running. Views read messages from a processor's journal directly. When a
@ -186,7 +186,7 @@ The default update interval of all views of an actor system is configurable:
interval for a specific view class or view instance. Applications may also trigger additional updates at interval for a specific view class or view instance. Applications may also trigger additional updates at
any time by sending a view an ``Update`` message. any time by sending a view an ``Update`` message.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#view-update .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#view-update
If the ``await`` parameter is set to ``true``, messages that follow the ``Update`` request are processed when the If the ``await`` parameter is set to ``true``, messages that follow the ``Update`` request are processed when the
incremental message replay, triggered by that update request, completed. If set to ``false`` (default), messages incremental message replay, triggered by that update request, completed. If set to ``false`` (default), messages
@ -234,7 +234,7 @@ destinations). The following discusses channels in context of processors but thi
Channels prevent redundant delivery of replayed messages to destinations during processor recovery. A replayed Channels prevent redundant delivery of replayed messages to destinations during processor recovery. A replayed
message is retained by a channel if its delivery has been confirmed by a destination. message is retained by a channel if its delivery has been confirmed by a destination.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#channel-example .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-example
A channel is ready to use once it has been created, no recovery or further activation is needed. A ``Deliver`` A channel is ready to use once it has been created, no recovery or further activation is needed. A ``Deliver``
request instructs a channel to send a ``Persistent`` message to a destination. A destination is provided as request instructs a channel to send a ``Persistent`` message to a destination. A destination is provided as
@ -244,7 +244,7 @@ preserved by a channel, therefore, a destination can reply to the sender of a ``
If a processor wants to reply to a ``Persistent`` message sender it should use the ``sender()`` path as If a processor wants to reply to a ``Persistent`` message sender it should use the ``sender()`` path as
channel destination. channel destination.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#channel-example-reply .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-example-reply
Persistent messages delivered by a channel are of type ``ConfirmablePersistent``. ``ConfirmablePersistent`` extends Persistent messages delivered by a channel are of type ``ConfirmablePersistent``. ``ConfirmablePersistent`` extends
``Persistent`` by adding the methods ``confirm`` and ``redeliveries`` (see also :ref:`redelivery-java-lambda`). A ``Persistent`` by adding the methods ``confirm`` and ``redeliveries`` (see also :ref:`redelivery-java-lambda`). A
@ -264,13 +264,13 @@ This timeout can be specified as ``redeliverInterval`` when creating a channel,
maximum number of re-deliveries a channel should attempt for each unconfirmed message. The number of re-delivery maximum number of re-deliveries a channel should attempt for each unconfirmed message. The number of re-delivery
attempts can be obtained via the ``redeliveries`` method on ``ConfirmablePersistent``. attempts can be obtained via the ``redeliveries`` method on ``ConfirmablePersistent``.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#channel-custom-settings .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-custom-settings
A channel keeps messages in memory until their successful delivery has been confirmed or the maximum number of A channel keeps messages in memory until their successful delivery has been confirmed or the maximum number of
re-deliveries is reached. To be notified about messages that have reached the maximum number of re-deliveries, re-deliveries is reached. To be notified about messages that have reached the maximum number of re-deliveries,
applications can register a listener at channel creation. applications can register a listener at channel creation.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#channel-custom-listener .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-custom-listener
A listener receives ``RedeliverFailure`` notifications containing all messages that could not be delivered. On A listener receives ``RedeliverFailure`` notifications containing all messages that could not be delivered. On
receiving a ``RedeliverFailure`` message, an application may decide to restart the sending processor to enforce receiving a ``RedeliverFailure`` message, an application may decide to restart the sending processor to enforce
@ -313,13 +313,13 @@ Persistent channels are like transient channels but additionally persist message
that have been persisted by a persistent channel are deleted when destinations confirm their delivery. A persistent that have been persisted by a persistent channel are deleted when destinations confirm their delivery. A persistent
channel can be created with ``PersistentChannel.props`` and configured with a ``PersistentChannelSettings`` object. channel can be created with ``PersistentChannel.props`` and configured with a ``PersistentChannelSettings`` object.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-example .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-example
A persistent channel is useful for delivery of messages to slow destinations or destinations that are unavailable A persistent channel is useful for delivery of messages to slow destinations or destinations that are unavailable
for a long time. It can constrain the number of pending confirmations based on the ``pendingConfirmationsMax`` for a long time. It can constrain the number of pending confirmations based on the ``pendingConfirmationsMax``
and ``pendingConfirmationsMin`` parameters of ``PersistentChannelSettings``. and ``pendingConfirmationsMin`` parameters of ``PersistentChannelSettings``.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-watermarks .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-watermarks
It suspends delivery when the number of pending confirmations reaches ``pendingConfirmationsMax`` and resumes It suspends delivery when the number of pending confirmations reaches ``pendingConfirmationsMax`` and resumes
delivery again when this number falls below ``pendingConfirmationsMin``. This prevents both, flooding destinations delivery again when this number falls below ``pendingConfirmationsMin``. This prevents both, flooding destinations
@ -335,7 +335,7 @@ case of a sender JVM crash is an issue, persistent channels should be used. In t
receive replies from the channel whether messages have been successfully persisted or not. This can be enabled by receive replies from the channel whether messages have been successfully persisted or not. This can be enabled by
creating the channel with the ``replyPersistent`` configuration parameter set to ``true``: creating the channel with the ``replyPersistent`` configuration parameter set to ``true``:
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-reply .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-reply
With this setting, either the successfully persisted message is replied to the sender or a ``PersistenceFailure`` With this setting, either the successfully persisted message is replied to the sender or a ``PersistenceFailure``
message. In case the latter case, the sender should re-send the message. message. In case the latter case, the sender should re-send the message.
@ -349,7 +349,7 @@ that channel's name in its actor hierarchy and hence influences only part of the
a channel identifier, it should be provided as argument ``Channel.props(String)`` or ``PersistentChannel.props(String)`` a channel identifier, it should be provided as argument ``Channel.props(String)`` or ``PersistentChannel.props(String)``
(recommended to generate stable identifiers). (recommended to generate stable identifiers).
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#channel-id-override .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-id-override
Persistent messages Persistent messages
=================== ===================
@ -362,7 +362,7 @@ must be derived from the current persistent message before sending them via a ch
or ``Persistent.create(..., getCurrentPersistentMessage())`` where ``getCurrentPersistentMessage()`` is defined on or ``Persistent.create(..., getCurrentPersistentMessage())`` where ``getCurrentPersistentMessage()`` is defined on
``AbstractProcessor``. ``AbstractProcessor``.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#current-message .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#current-message
This is necessary for delivery confirmations to work properly. Both This is necessary for delivery confirmations to work properly. Both
ways are equivalent but we recommend using ``p.withPayload(...)`` for clarity. It is not allowed to send a message ways are equivalent but we recommend using ``p.withPayload(...)`` for clarity. It is not allowed to send a message
@ -387,12 +387,12 @@ in context of processors but this is also applicable to views.
Processors can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot Processors can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot
succeeds, the processor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message succeeds, the processor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#save-snapshot .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#save-snapshot
During recovery, the processor is offered a previously saved snapshot via a ``SnapshotOffer`` message from During recovery, the processor is offered a previously saved snapshot via a ``SnapshotOffer`` message from
which it can initialize internal state. which it can initialize internal state.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-offer .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-offer
The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot. The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot.
They finally recover the processor to its current (i.e. latest) state. They finally recover the processor to its current (i.e. latest) state.
@ -400,7 +400,7 @@ They finally recover the processor to its current (i.e. latest) state.
In general, a processor is only offered a snapshot if that processor has previously saved one or more snapshots In general, a processor is only offered a snapshot if that processor has previously saved one or more snapshots
and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery. and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-criteria .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-criteria
If not specified, they default to ``SnapshotSelectionCriteria.latest()`` which selects the latest (= youngest) snapshot. If not specified, they default to ``SnapshotSelectionCriteria.latest()`` which selects the latest (= youngest) snapshot.
To disable snapshot-based recovery, applications should use ``SnapshotSelectionCriteria.none()``. A recovery where no To disable snapshot-based recovery, applications should use ``SnapshotSelectionCriteria.none()``. A recovery where no
@ -441,7 +441,7 @@ event sourcing as a pattern on top of command sourcing). A processor that extend
``AbstractEventsourcedProcessor`` is defined by implementing ``receiveRecover`` and ``receiveCommand``. This is ``AbstractEventsourcedProcessor`` is defined by implementing ``receiveRecover`` and ``receiveCommand``. This is
demonstrated in the following example. demonstrated in the following example.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/sample/persistence/EventsourcedExample.java#eventsourced-example .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/sample/persistence/EventsourcedExample.java#eventsourced-example
The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The
``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``. ``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``.
@ -462,13 +462,20 @@ about successful state changes by publishing events.
When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist`` the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
calls in context of a single command. The example also shows how to switch between command different command handlers calls in context of a single command.
with ``context().become()`` and ``context().unbecome()``.
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_ The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
and open the tutorial named `Akka Persistence Samples with Java <http://www.typesafe.com/activator/template/akka-sample-persistence-java8>`_. and open the tutorial named `Akka Persistence Samples in Java with Lambdas <http://www.typesafe.com/activator/template/akka-sample-persistence-java-lambda>`_.
It contains instructions on how to run the ``EventsourcedExample``. It contains instructions on how to run the ``EventsourcedExample``.
.. note::
It's also possible to switch between different command handlers during normal processing and recovery
with ``context().become()`` and ``context().unbecome()``. To get the actor into the same state after
recovery you need to take special care to perform the same state transitions with ``become`` and
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
Reliable event delivery Reliable event delivery
----------------------- -----------------------
@ -476,7 +483,7 @@ Sending events from an event handler to another actor has at-most-once delivery
:ref:`channels-java-lambda` must be used. In this case, also replayed events (received by ``receiveRecover``) must be :ref:`channels-java-lambda` must be used. In this case, also replayed events (received by ``receiveRecover``) must be
sent to a channel, as shown in the following example: sent to a channel, as shown in the following example:
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#reliable-event-delivery .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#reliable-event-delivery
In larger integration scenarios, channel destinations may be actors that submit received events to an external In larger integration scenarios, channel destinations may be actors that submit received events to an external
message broker, for example. After having successfully submitted an event, they should call ``confirm()`` on the message broker, for example. After having successfully submitted an event, they should call ``confirm()`` on the
@ -498,7 +505,7 @@ writing the previous batch. Batch writes are never timer-based which keeps laten
Applications that want to have more explicit control over batch writes and batch sizes can send processors Applications that want to have more explicit control over batch writes and batch sizes can send processors
``PersistentBatch`` messages. ``PersistentBatch`` messages.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistenceDocTest.java#batch-write .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#batch-write
``Persistent`` messages contained in a ``PersistentBatch`` are always written atomically, even if the batch ``Persistent`` messages contained in a ``PersistentBatch`` are always written atomically, even if the batch
size is greater than ``max-message-batch-size``. Also, a ``PersistentBatch`` is written isolated from other batches. size is greater than ``max-message-batch-size``. Also, a ``PersistentBatch`` is written isolated from other batches.
@ -522,7 +529,7 @@ snapshots as individual files to the local filesystem (see :ref:`local-snapshot-
provide their own plugins by implementing a plugin API and activate them by configuration. Plugin development provide their own plugins by implementing a plugin API and activate them by configuration. Plugin development
requires the following imports: requires the following imports:
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistencePluginDocTest.java#plugin-imports .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistencePluginDocTest.java#plugin-imports
Journal plugin API Journal plugin API
------------------ ------------------
@ -611,7 +618,7 @@ plugin.
This plugin must be initialized by injecting the (remote) ``SharedLeveldbStore`` actor reference. Injection is This plugin must be initialized by injecting the (remote) ``SharedLeveldbStore`` actor reference. Injection is
done by calling the ``SharedLeveldbJournal.setStore`` method with the actor reference as argument. done by calling the ``SharedLeveldbJournal.setStore`` method with the actor reference as argument.
.. includecode:: ../../../akka-samples/akka-sample-persistence-java8/src/main/java/doc/LambdaPersistencePluginDocTest.java#shared-store-usage .. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistencePluginDocTest.java#shared-store-usage
Internal journal commands (sent by processors) are buffered until injection completes. Injection is idempotent Internal journal commands (sent by processors) are buffered until injection completes. Injection is idempotent
i.e. only the first injection is used. i.e. only the first injection is used.

View file

@ -487,13 +487,20 @@ about successful state changes by publishing events.
When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist`` the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
calls in context of a single command. The example also shows how to switch between command different command handlers calls in context of a single command.
with ``getContext().become()`` and ``getContext().unbecome()``.
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_ The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
and open the tutorial named `Akka Persistence Samples with Java <http://www.typesafe.com/activator/template/akka-sample-persistence-java>`_. and open the tutorial named `Akka Persistence Samples with Java <http://www.typesafe.com/activator/template/akka-sample-persistence-java>`_.
It contains instructions on how to run the ``EventsourcedExample``. It contains instructions on how to run the ``EventsourcedExample``.
.. note::
It's also possible to switch between different command handlers during normal processing and recovery
with ``getContext().become()`` and ``getContext().unbecome()``. To get the actor into the same state after
recovery you need to take special care to perform the same state transitions with ``become`` and
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
Reliable event delivery Reliable event delivery
----------------------- -----------------------

View file

@ -497,13 +497,20 @@ about successful state changes by publishing events.
When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist`` the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
calls in context of a single command. The example also shows how to switch between command different command handlers calls in context of a single command.
with ``context.become()`` and ``context.unbecome()``.
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_ The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
and open the tutorial named `Akka Persistence Samples with Scala <http://www.typesafe.com/activator/template/akka-sample-persistence-scala>`_. and open the tutorial named `Akka Persistence Samples with Scala <http://www.typesafe.com/activator/template/akka-sample-persistence-scala>`_.
It contains instructions on how to run the ``EventsourcedExample``. It contains instructions on how to run the ``EventsourcedExample``.
.. note::
It's also possible to switch between different command handlers during normal processing and recovery
with ``context.become()`` and ``context.unbecome()``. To get the actor into the same state after
recovery you need to take special care to perform the same state transitions with ``become`` and
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
Reliable event delivery Reliable event delivery
----------------------- -----------------------

View file

@ -23,10 +23,14 @@ private[persistence] trait Eventsourced extends Processor {
* `processingCommands` * `processingCommands`
*/ */
private val recovering: State = new State { private val recovering: State = new State {
// cache the recoveryBehavior since it's a def for binary compatibility in 2.3.x
private val _recoveryBehavior: Receive = recoveryBehavior
override def toString: String = "recovering" override def toString: String = "recovering"
def aroundReceive(receive: Receive, message: Any) { def aroundReceive(receive: Receive, message: Any) {
Eventsourced.super.aroundReceive(receive, message) // Since we are recovering we can ignore the receive behavior from the stack
Eventsourced.super.aroundReceive(_recoveryBehavior, message)
message match { message match {
case _: ReadHighestSequenceNrSuccess | _: ReadHighestSequenceNrFailure case _: ReadHighestSequenceNrSuccess | _: ReadHighestSequenceNrFailure
currentState = processingCommands currentState = processingCommands
@ -93,6 +97,21 @@ private[persistence] trait Eventsourced extends Processor {
} }
} }
/**
* INTERNAL API.
*
* This is a def and not a val because of binary compatibility in 2.3.x.
* It is cached where it is used.
*/
private def recoveryBehavior: Receive = {
case Persistent(payload, _) if recoveryRunning && receiveRecover.isDefinedAt(payload)
receiveRecover(payload)
case s: SnapshotOffer if receiveRecover.isDefinedAt(s)
receiveRecover(s)
case f: RecoveryFailure if receiveRecover.isDefinedAt(f)
receiveRecover(f)
}
private var persistInvocations: List[(Any, Any Unit)] = Nil private var persistInvocations: List[(Any, Any Unit)] = Nil
private var persistentEventBatch: List[PersistentRepr] = Nil private var persistentEventBatch: List[PersistentRepr] = Nil
@ -190,14 +209,10 @@ private[persistence] trait Eventsourced extends Processor {
/** /**
* INTERNAL API. * INTERNAL API.
*
* Only here for binary compatibility in 2.3.x.
*/ */
protected[persistence] val initialBehavior: Receive = { protected[persistence] val initialBehavior: Receive = recoveryBehavior orElse {
case Persistent(payload, _) if receiveRecover.isDefinedAt(payload) && recoveryRunning
receiveRecover(payload)
case s: SnapshotOffer if receiveRecover.isDefinedAt(s)
receiveRecover(s)
case f: RecoveryFailure if receiveRecover.isDefinedAt(f)
receiveRecover(f)
case msg if receiveCommand.isDefinedAt(msg) case msg if receiveCommand.isDefinedAt(msg)
receiveCommand(msg) receiveCommand(msg)
} }
@ -207,14 +222,14 @@ private[persistence] trait Eventsourced extends Processor {
* An event sourced processor. * An event sourced processor.
*/ */
trait EventsourcedProcessor extends Processor with Eventsourced { trait EventsourcedProcessor extends Processor with Eventsourced {
final def receive = initialBehavior final def receive = receiveCommand
} }
/** /**
* Java API: an event sourced processor. * Java API: an event sourced processor.
*/ */
abstract class UntypedEventsourcedProcessor extends UntypedProcessor with Eventsourced { abstract class UntypedEventsourcedProcessor extends UntypedProcessor with Eventsourced {
final def onReceive(message: Any) = initialBehavior(message) final def onReceive(message: Any) = onReceiveCommand(message)
final def receiveRecover: Receive = { final def receiveRecover: Receive = {
case msg onReceiveRecover(msg) case msg onReceiveRecover(msg)

View file

@ -137,6 +137,23 @@ object EventsourcedSpec {
} }
} }
class SnapshottingBecomingEventsourcedProcessor(name: String, probe: ActorRef) extends SnapshottingEventsourcedProcessor(name, probe) {
val becomingRecover: Receive = {
case msg: SnapshotOffer
context.become(becomingCommand)
// sending ourself a normal message here also tests
// that we stash them until recovery is complete
self ! "It's changing me"
super.receiveRecover(msg)
}
override def receiveRecover = becomingRecover.orElse(super.receiveRecover)
val becomingCommand: Receive = receiveCommand orElse {
case "It's changing me" probe ! "I am becoming"
}
}
class ReplyInEventHandlerProcessor(name: String) extends ExampleProcessor(name) { class ReplyInEventHandlerProcessor(name: String) extends ExampleProcessor(name) {
val receiveCommand: Receive = { val receiveCommand: Receive = {
case Cmd("a") persist(Evt("a"))(evt sender ! evt.data) case Cmd("a") persist(Evt("a"))(evt sender ! evt.data)
@ -297,6 +314,21 @@ abstract class EventsourcedSpec(config: Config) extends AkkaSpec(config) with Pe
processor2 ! GetState processor2 ! GetState
expectMsg(List("a-1", "a-2", "b-41", "b-42", "c-41", "c-42")) expectMsg(List("a-1", "a-2", "b-41", "b-42", "c-41", "c-42"))
} }
"support context.become during recovery" in {
val processor1 = system.actorOf(Props(classOf[SnapshottingEventsourcedProcessor], name, testActor))
processor1 ! Cmd("b")
processor1 ! "snap"
processor1 ! Cmd("c")
expectMsg("saved")
processor1 ! GetState
expectMsg(List("a-1", "a-2", "b-41", "b-42", "c-41", "c-42"))
val processor2 = system.actorOf(Props(classOf[SnapshottingBecomingEventsourcedProcessor], name, testActor))
expectMsg("offered")
expectMsg("I am becoming")
processor2 ! GetState
expectMsg(List("a-1", "a-2", "b-41", "b-42", "c-41", "c-42"))
}
"be able to reply within an event handler" in { "be able to reply within an event handler" in {
val processor = namedProcessor[ReplyInEventHandlerProcessor] val processor = namedProcessor[ReplyInEventHandlerProcessor]
processor ! Cmd("a") processor ! Cmd("a")

View file

@ -0,0 +1,7 @@
name=akka-sample-persistence-java-lambda
title=Akka Persistence Samples in Java with Lambdas
description=Akka Persistence Samples in Java with Lambdas
tags=akka,java,java8,sample,persistence,sample
authorName=Akka Team
authorLink=http://akka.io/
sourceLink=https://github.com/akka/akka

View file

@ -0,0 +1,12 @@
name := "akka-sample-persistence-java-lambda"
version := "1.0"
scalaVersion := "2.10.3"
javacOptions ++= Seq("-source", "1.8", "-target", "1.8", "-Xlint")
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-persistence-experimental" % "2.3-SNAPSHOT"
)

View file

@ -0,0 +1 @@
sbt.version=0.13.1

View file

@ -95,23 +95,12 @@ class ExampleProcessor extends AbstractEventsourcedProcessor {
state.update(evt); state.update(evt);
if (evt.equals(evt2)) { if (evt.equals(evt2)) {
context().system().eventStream().publish(evt); context().system().eventStream().publish(evt);
if (data.equals("foo")) { context().become(och, true); }
} }
}); });
}). }).
match(String.class, s -> s.equals("snap"), s -> saveSnapshot(state.copy())). match(String.class, s -> s.equals("snap"), s -> saveSnapshot(state.copy())).
match(String.class, s -> s.equals("print"), s -> System.out.println(state)).build(); match(String.class, s -> s.equals("print"), s -> System.out.println(state)).build();
} }
PartialFunction<Object, BoxedUnit> och = ReceiveBuilder.
match(Cmd.class, cmd -> cmd.getData().equals("bar"), cmd -> {
persist(new Evt("bar-" + getNumEvents()), event -> {
state.update(event);
context().unbecome();
});
unstashAll();
}).
matchAny(o -> stash()).build();
} }
//#eventsourced-example //#eventsourced-example

View file

@ -88,7 +88,6 @@ class ExampleProcessor extends UntypedEventsourcedProcessor {
state.update(evt); state.update(evt);
if (evt.equals(evt2)) { if (evt.equals(evt2)) {
getContext().system().eventStream().publish(evt); getContext().system().eventStream().publish(evt);
if (data.equals("foo")) getContext().become(otherCommandHandler);
} }
} }
}); });
@ -100,22 +99,6 @@ class ExampleProcessor extends UntypedEventsourcedProcessor {
System.out.println(state); System.out.println(state);
} }
} }
private Procedure<Object> otherCommandHandler = new Procedure<Object>() {
public void apply(Object msg) throws Exception {
if (msg instanceof Cmd && ((Cmd)msg).getData().equals("bar")) {
persist(new Evt("bar-" + getNumEvents()), new Procedure<Evt>() {
public void apply(Evt event) throws Exception {
state.update(event);
getContext().unbecome();
}
});
unstashAll();
} else {
stash();
}
}
};
} }
//#eventsourced-example //#eventsourced-example

View file

@ -33,21 +33,11 @@ class ExampleProcessor extends EventsourcedProcessor {
persist(Evt(s"${data}-${numEvents + 1}")) { event => persist(Evt(s"${data}-${numEvents + 1}")) { event =>
updateState(event) updateState(event)
context.system.eventStream.publish(event) context.system.eventStream.publish(event)
if (data == "foo") context.become(otherCommandHandler)
} }
case "snap" => saveSnapshot(state) case "snap" => saveSnapshot(state)
case "print" => println(state) case "print" => println(state)
} }
val otherCommandHandler: Receive = {
case Cmd("bar") =>
persist(Evt(s"bar-${numEvents}")) { event =>
updateState(event)
context.unbecome()
}
unstashAll()
case other => stash()
}
} }
//#eventsourced-example //#eventsourced-example
@ -57,7 +47,7 @@ object EventsourcedExample extends App {
val processor = system.actorOf(Props[ExampleProcessor], "processor-4-scala") val processor = system.actorOf(Props[ExampleProcessor], "processor-4-scala")
processor ! Cmd("foo") processor ! Cmd("foo")
processor ! Cmd("baz") // will be stashed processor ! Cmd("baz")
processor ! Cmd("bar") processor ! Cmd("bar")
processor ! "snap" processor ! "snap"
processor ! Cmd("buzz") processor ! Cmd("buzz")

View file

@ -100,6 +100,6 @@ mvncleantest "$java8_home" "akka-samples/akka-docs-java-lambda"
mvncleantest "$java8_home" "akka-samples/akka-sample-fsm-java-lambda" mvncleantest "$java8_home" "akka-samples/akka-sample-fsm-java-lambda"
mvncleantest "$java8_home" "akka-samples/akka-sample-persistence-java8" mvncleantest "$java8_home" "akka-samples/akka-sample-persistence-java-lambda"
mvncleantest "$java8_home" "akka-samples/akka-sample-supervision-java-lambda" mvncleantest "$java8_home" "akka-samples/akka-sample-supervision-java-lambda"