Lightbend Activator

eventuate with Scala!

Activator will be EOL-ed on May 24, 2017.

We’re making it easier and simpler for developers to get started with Lightbend technologies. This unfortunately means that future releases of Play, Akka and Scala will no longer include Activator support, and Lightbend’s Activator server will be decommissioned by the end of 2017. Instead of supporting Activator to create and set up development projects, we'll be supporting standard Giter8 templates for sbt users and Maven archetypes for Maven users. So going forward,

To create new Lightbend projects

Instead of using the Activator command, make sure you have sbt 0.13.13 (or higher), and use the “sbt new” command, providing the name of the template. For example, “$ sbt new akka/hello-akka.g8”. You can find a list of templates here.

Also, as a convenience, the Lightbend Project Starter allows you to quickly create a variety of example projects that you just unzip and run.

To create new templates

If you want to create new templates, you can now do that in Giter8.

To migrate templates from Activator to Giter8

If you created Activator templates in the past, please consider migrating them to Giter8 with this simple process.

eventuate with Scala!

volkerstampa
Source
November 11, 2016
akka scala sample eventuate

Illustrates eventsourced distributed application based on eventuate

How to get "eventuate with Scala!" on your computer

There are several ways to get this template.

Option 1: Choose akka-eventuate-scala in the Lightbend Activator UI.

Already have Lightbend Activator (get it here)? Launch the UI then search for akka-eventuate-scala in the list of templates.

Option 2: Download the akka-eventuate-scala project as a zip archive

If you haven't installed Activator, you can get the code by downloading the template bundle for akka-eventuate-scala.

  1. Download the Template Bundle for "eventuate with Scala!"
  2. Extract the downloaded zip file to your system
  3. The bundle includes a small bootstrap script that can start Activator. To start Lightbend Activator's UI:

    In your File Explorer, navigate into the directory that the template was extracted to, right-click on the file named "activator.bat", then select "Open", and if prompted with a warning, click to continue:

    Or from a command line:

     C:\Users\typesafe\akka-eventuate-scala> activator ui 
    This will start Lightbend Activator and open this template in your browser.

Option 3: Create a akka-eventuate-scala project from the command line

If you have Lightbend Activator, use its command line mode to create a new project from this template. Type activator new PROJECTNAME akka-eventuate-scala on the command line.

Option 4: View the template source

The creator of this template maintains it at https://github.com/volkerstampa/akka-eventuate-scala#master.

Option 5: Preview the tutorial below

We've included the text of this template's tutorial below, but it may work better if you view it inside Activator on your computer. Activator tutorials are often designed to be interactive.

Preview the tutorial

eventuate tutorial

This tutorial guides you through a distributed order management application based on eventuate by using distributed and event-sourced order-actors. It demonstrates the most important parts of such an application and how to build one yourself. The example is actually taken from the eventuate project itself and only modified slightly to allow better integration into an activator template.

The application runs an OrderManager on several nodes. Each one is able to accept

  • new orders
  • new order items for an existing order
  • cancellation of orders
  • removal of items from an existing order

The changes are replicated to all connected nodes. Changing the same order on multiple nodes concurrently is considered a conflict. In that case multiple versions of this order are maintained until one is picked interactively to resolve the conflict.

The application: OrderLocation

The entry into the application is OrderLocation. It starts the actor-system and the actors OrderManager as well as OrderView in the akka-typical manner. What makes it an eventuate-based application that supports distributed deployment is the ReplicationEndpoint. This takes care of replicating changes to all connected ReplicationEndpoints. A ReplicationEndpoint can either be setup programmatically or (as in this case) by configuration. For this on each node akka-remoting host and port (under which this RelicationEndpoint is available) needs to be defined:


akka.remote.netty.tcp.hostname = "127.0.0.1"
akka.remote.netty.tcp.port=2554
    

as well as an endpoint-id and the replication-partners on the other nodes:


eventuate {
  endpoint.id = "C"
  endpoint.connections = ["127.0.0.1:2552", "127.0.0.1:2553", "127.0.0.1:2555"]
}
    

So in this case ReplicationEndpoint C listens on 127.0.0.1:2554 and connects to ReplicationEndpoints on other nodes listening on 127.0.0.1:2552, 127.0.0.1:2553 and 127.0.0.1:2555.

The order manager

At the heart of the application is the OrderManager. It is implemented as EventsourcedView to better illustrate how orders are created in the distributed system. For the moment it is enough to assume that it is a plain actor and its onCommand method is actually the receive method of an actor.

Its main purpose is to maintain a Map of OrderActors by id each one representing a specific order and dispatch OrderCommands or a Resolve-command to them (to modify the order) or a SaveSnapshot-command for taking a snapshot of the internal state of an order. Additionally it allows to retrieve the current state of all orders (currently in memory).

The order-actor

A single order is represented by an OrderActor. It is implemented as an EventsourcedActor. It represents a special case of an aggregate root and that is why it defines the member aggregateId. This has to be an application wide unique id. Even eventsourced aggregate roots of different type must not share the same ids. In addition to this it defines the members id and eventLog. id must be a globally unique id, so all other EventsourcedActors or even EventsourcedViews in the entire distributed setup must not share this id. Here it is built as combination of replicaId that identifies a node or location and orderId. The idea is that there are several OrderActor-instances with the same orderId (respectively aggregateId) at different nodes (and thus with different replicaId) and state-changes to one of them are replicated to the others so that they are eventually all in the same state. For this an EventsourcedActor defines the two methods:

  • onCommand
  • onEvent

The purpose of onCommand is to process commands (of type OrderCommand) that sre sent to change the state of the order (like AddOrderItem) by

  • verifying that the command is valid and can be accepted and in case it is
  • emitting an event (or multiple) that describes the state-change (by calling persist(event))

So for example if an AddOrderItem-command can be accepted the onCommand-handler emits an OrderItemAdded-event. Before this event is processed by the onEvent-handler to perform the requested state-change, it is persisted by eventuate to the event-log (represented by the member eventLog).

This reflects the core principle of eventsourced applications. Instead of persisting the current state, state-changes are persisted. When the application (or just the actor) is restarted all persisted events are replayed (i.e. the onEvent-handler is called for each of them) to reconstruct the state as before.

Note that onCommand and onEvent are both called during normal actor message dispatch so they can safely access and modify an actor's mutable state, however as persisting is done asynchronously they are not invoked through a single message. Nonetheless eventuate guarantees by default that no new command slips between an onCommand-call and the onEvent-calls for each emitted/persisted event by stashing commands that arrive in the meantime. This guarantee can be relaxed for performance reasons.

eventuate provides two additional important features:

  1. Once persisted it replicates the event to all connected nodes and calls the onEvent-handler of all affected eventsourced-actors on all nodes. In this case the affected eventsourced actor is an (already active) OrderActor with the same aggregate-id.
  2. It tracks causality of events, i.e. it ensures that an event that is known to has happened before another event is consumed by all affected actors' onEvent-handler before the other event

The order-view

Another typical element of eventsourced application are so called eventsourced views. These are actors that consume events (to build up internal state) but cannot emit any. They can be used to implement the query-side of a CQRS based application.

The OrderView implements a simple example of such an EventsourcedView. As it does not define aggregateId it consumes all events that are written to its eventLog either directly by eventsourced actors on the same node or through replication.

Here the OrderView simply counts for each order its updates (i.e. the emitted OrderEvents) and allows to query for this number by order-id.

Running the example

On the Run-tab you can start an example-setup with two locations (C and D) running in a single JVM in action (main file: sample.eventuate.OrderBot). A bot will send commands alternating to the OrderManagers of both locations.

When you start it the first time you will see something like this in the log-output:


( 1) 15:40:16.965 CreateOrderAction$: ------- Send CreateOrder to location-D (1/9) -------
( 1) 15:40:16.966 OrderManager: [D]: Process command: CreateOrder(0762ee15-c570-4c57-8b03-bf757418df9f)
( 2) 15:40:16.966 OrderManager: [D]: Create OrderActor for 0762ee15-c570-4c57-8b03-bf757418df9f
( 3) 15:40:16.971 OrderActor: [D]: OrderCreated: [0762ee15-c570-4c57-8b03-bf757418df9f] items= cancelled=false
( 5) 15:40:16.978 OrderManager: [C]: Create OrderActor for 0762ee15-c570-4c57-8b03-bf757418df9f
( 6) 15:40:16.981 OrderActor: [C]: Initialized from Log: [0762ee15-c570-4c57-8b03-bf757418df9f] items= cancelled=false
( 7) 15:40:19.978 AddOrderItemAction$: ------- Send AddOrderItem to location-C (2/9) --------
( 8) 15:40:19.978 OrderManager: [C]: Process command: AddOrderItem(0762ee15-c570-4c57-8b03-bf757418df9f,Fqmtv)
( 9) 15:40:19.984 OrderActor: [C]: OrderItemAdded: [0762ee15-c570-4c57-8b03-bf757418df9f] items=Fqmtv cancelled=false
(10) 15:40:19.990 OrderActor: [D]: OrderItemAdded: [0762ee15-c570-4c57-8b03-bf757418df9f] items=Fqmtv cancelled=false
        

This shows that:

  1. A CreateOrder-command is sent to D.
  2. D's OrderManager starts the OrderActor-accordingly.
  3. The OrderActor emits the OrderCreated event.
  4. This event gets replicated to location C.
  5. C's OrderManager sees it and eagerly starts a corresponding OrderActor.
  6. This OrderActor gets initialized from C's event log that already contains the replicated event and thus ends up in the same state as D's order.
  7. After a short pause an AddOrderItem-command for this order is sent to C.
  8. C's OrderManager dispatches the command to the OrderActor,
  9. which emits a corresponding OrderItemAdded-event
  10. The event gets replicated to D und the (already existing) OrderActor can consume the same event to bring itself into the same state as C's order.

Concurrent events and conflicts

Now that you have seen the example application you may wonder what happens if an item is added to the same order on both locations C and D simultaneously. By tracking causality of events eventuate can detect concurrent events and thus potentially conflicting updates.

You can actually try this out by commenting the sleep-statement in OrderBot and running the example again. As the activator UI gives access to a limited number of log-lines only, it makes sense to test this using sbt started from a terminal. For this execute:

sbt "runMain sample.eventuate.OrderBot"

Depending on your hardware you may need a couple of tries or it might even make sense to increase the total number of commands sent to the application (sample.eventuate.Action#total), but eventually you should find something like this in the output:


16:25:59.248 OrderActor: [C]: OrderItemAdded: Conflict:
- version 0: [5f789bc3-9a20-4741-a6c6-dd87cfe36042] items=XN7iM,3hZQO cancelled=false
- version 1: [5f789bc3-9a20-4741-a6c6-dd87cfe36042] items=XN7iM,T6dEy cancelled=false
...
16:25:59.252 OrderActor: [D]: OrderItemAdded: Conflict:
- version 0: [5f789bc3-9a20-4741-a6c6-dd87cfe36042] items=XN7iM,T6dEy cancelled=false
- version 1: [5f789bc3-9a20-4741-a6c6-dd87cfe36042] items=XN7iM,3hZQO cancelled=false
    

In this case the bot added to the order with id 5f789bc3... (which already contained item XN7iM) simultaneously the items 3hZQO and T6dEy. As the bot actually runs purely sequential, simultaneously in this case means the items were added on nodes C and D before the corresponding event was replicated to the other location.

By using ConcurrentVersions an actor is able to maintain a tree of conflicting versions. These conflicts can be resolved by a selecting a winner-version either automatically or interactively. See the section on resolving conflicts in the example application for an interactive example.

Testing the application

While seeing the replication and conflict detection in a log-file is nice, you would typically want to write automated tests to check correct behaviour of the application in these circumstances.

For this akka comes with the amazing multi-node-testing-toolkit and OrderSpec uses exactly this to verify that the application behaves as expected when distributed to two JVMs.

A multi-jvm test basically consists out of two parts:

  • A MultiNodeConfig defining the common configuration and the nodes that take part in the test
  • A (or several) MultiNodeSpec(s) defining the actual test-logic.

The MultiNodeConfig TwoNodesReplicationConfig defines two roles nodeA and nodeB which are equivalent to nodes in the test. Additionally it configures replication properties that ensure better timing for testing than the default ones.

The actual test-code can be found in OrderSpec. Each test uses the method withTwoOrderManagers (implementing the loan-fixture-pattern) to get a MultiNodeSpec-reference that sets up the replication for the two nodes (running in two JVMs!) and starts an OrderManager (on each node). In addition this reference comes with some convenience methods that ease testing the OrderManager. The actual test-code comes in the withTwoOrderManagers-block and is executed on both JVMs simultaneously.

Create Orders on both nodes

The first test simply sends the CreateOder-command to the local OrderManager (on each node)


executeCommand(CreateOrder(newOrderId))
    

waits for both OrderCreated-events (on each node)


val emittedEvents = listener.expectMsgAllClassOf(
  List.fill(2)(classOf[OrderCreated]): _*)
    

and verifies (on each node) that the OrderManagers contain both orders


allOrders shouldBe emittedEvents.map(toOrder).toSet
    

Create Order on one node, add item on the other

The second test creates the order only on node A


runOn(config.nodeA)(executeCommand(CreateOrder(newOrderId)))
    

waits for the order to be created (on each node)


val OrderCreated(orderId, _) = waitFor(orderCreated)
    

and adds an item to the order only on node B


runOn(config.nodeB)(executeCommand(AddOrderItem(orderId, "item")))
    

At the end the OrderManager (on each node) must contain the same order.

Add item on both nodes simultaneously

The third test finally provokes a conflict by

  1. creating an order on nodeA (and waiting for replication)
  2. disconnecting both nodes (i.e. simulating a network partition)
  3. adding an order on each node (which creates the conflict)
  4. connecting the nodes again

Once the connection is reestablished the added items are replicated to the other node and (since they took place concurrently) result in a conflict on each node. At the end the test verifies (on each node) that the OrderManager contain both versions of the order.

Resolving conflicts

To demonstrate interactive conflict resolution one can use the OrderExample started from a terminal. For this execute in the root-directory of the project (Linux and Mac only):


./example A B C
    

to start three nodes in three terminal windows on your machine. With


create Order1
    

in any of the windows you can create an order and see that it gets replicated to all nodes. Once that is done you can add an item in another window:


add Order1 Item1
    

Now lets create a partition by stopping node C (Ctrl-C) and add different item in A's and B's window:


(in A:) add Order1 ItemA
(in B:) add Order1 ItemB
    

Restart C (Cursor Up + Enter) to re-enable replication and you should see something as follows in all windows:


14:17:46.863 OrderActor: [?]: OrderItemAdded: Conflict:
- version 0: [Order1] items=Item1,ItemA cancelled=false
- version 1: [Order1] items=Item1,ItemB cancelled=false
    

The conflict can be resolved with the following command:


resolve Order1 0
    

To avoid conflicting resolutions the example application implements the rule that a conflict can only be resolved on the node that initially created the order. This command sends an Resolve(Order1, 0) (defined in the eventuate-library) to the OrderManager which in turn forwards it to the corresponding OrderActor. When the command can be accepted a corresponding Resolved(Order1, vector-timestamp) event is emitted and consumed and processed by all nodes.

×

Welcome to the Lightbend Enterprise Suite


You are excited about Reactive applications. And you want to build, manage and monitor them easily and reliably in your environment.
We get it. So we built Lightbend Enterprise Suite to help you do that, and more, with a range of powerful Application Management, Intelligent Monitoring, Enterprise Integration and Advanced Tooling features.