├── .gitattributes ├── Akka.NET └── Accounts │ ├── Account.cs │ └── AccountTest.cs ├── DDDwithActors.pdf ├── Elixir ├── Advanced │ ├── account.ex │ ├── account_run.exs │ └── account_test.exs ├── account.ex └── account_run.exs ├── Erlang ├── account.erl ├── account_record.hrl └── account_runner.erl ├── README.md └── ScalaAkka ├── build.sbt └── src └── main ├── resources └── application.conf └── scala ├── Account.scala └── AccountRunner.scala /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Akka.NET/Accounts/Account.cs: -------------------------------------------------------------------------------- 1 | namespace Accounts 2 | { 3 | using System; 4 | using Akka; 5 | using Akka.Actor; 6 | 7 | // Commands 8 | public class OpenAccount 9 | { 10 | public OpenAccount(string accountNumber, int initialBalance) 11 | { 12 | AccountNumber = accountNumber; 13 | InitialBalance = initialBalance; 14 | } 15 | 16 | public string AccountNumber { get; private set; } 17 | public int InitialBalance { get; private set; } 18 | } 19 | 20 | public class DepositFunds 21 | { 22 | public DepositFunds(int amount) 23 | { 24 | Amount = amount; 25 | } 26 | 27 | public int Amount { get; private set; } 28 | } 29 | 30 | public class WithdrawFunds 31 | { 32 | public WithdrawFunds(int amount) 33 | { 34 | Amount = amount; 35 | } 36 | 37 | public int Amount { get; private set; } 38 | } 39 | 40 | // Events 41 | public class AccountOpened 42 | { 43 | public AccountOpened(string accountNumber, int initialBalance) 44 | { 45 | AccountNumber = accountNumber; 46 | InitialBalance = initialBalance; 47 | } 48 | 49 | public string AccountNumber { get; private set; } 50 | public int InitialBalance { get; private set; } 51 | } 52 | 53 | public class FundsDeposited 54 | { 55 | public FundsDeposited(string accountNumber, int amount) 56 | { 57 | AccountNumber = accountNumber; 58 | Amount = amount; 59 | } 60 | 61 | public string AccountNumber { get; private set; } 62 | public int Amount { get; private set; } 63 | } 64 | 65 | public class FundsWithdrawn 66 | { 67 | public FundsWithdrawn(string accountNumber, int amount) 68 | { 69 | AccountNumber = accountNumber; 70 | Amount = amount; 71 | } 72 | 73 | public string AccountNumber { get; private set; } 74 | public int Amount { get; private set; } 75 | } 76 | 77 | public class AccountState 78 | { 79 | public AccountState(string accountNumber, int balance) 80 | { 81 | AccountNumber = accountNumber; 82 | Balance = balance; 83 | } 84 | 85 | internal string AccountNumber { get; private set; } 86 | internal int Balance { get; private set; } 87 | 88 | internal AccountState FromDeposited(int amount) 89 | { 90 | return new AccountState(AccountNumber, Balance + amount); 91 | } 92 | 93 | internal AccountState FromWithdrawn(int amount) 94 | { 95 | return new AccountState(AccountNumber, Balance - amount); 96 | } 97 | } 98 | 99 | public class Account : TypedActor, IHandle, IHandle, IHandle 100 | { 101 | public Account() 102 | { 103 | } 104 | 105 | public void Handle(OpenAccount open) 106 | { 107 | State = new AccountState(open.AccountNumber, open.InitialBalance); 108 | var newEvent = new AccountOpened(open.AccountNumber, open.InitialBalance); 109 | var answer = new Tuple(State, newEvent); 110 | 111 | Sender.Tell(answer, Sender); 112 | } 113 | 114 | public void Handle(DepositFunds deposit) 115 | { 116 | State = State.FromDeposited(deposit.Amount); 117 | var newEvent = new FundsDeposited(State.AccountNumber, deposit.Amount); 118 | var answer = new Tuple(State, newEvent); 119 | 120 | Sender.Tell(answer, Sender); 121 | } 122 | 123 | public void Handle(WithdrawFunds withdraw) 124 | { 125 | State = State.FromWithdrawn(withdraw.Amount); 126 | var newEvent = new FundsWithdrawn(State.AccountNumber, withdraw.Amount); 127 | var answer = new Tuple(State, newEvent); 128 | 129 | Sender.Tell(answer, Sender); 130 | } 131 | 132 | public AccountState State { get; private set; } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Akka.NET/Accounts/AccountTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | using Akka; 6 | using Akka.Actor; 7 | 8 | namespace Accounts 9 | { 10 | [TestClass] 11 | public class AccountTest 12 | { 13 | [TestMethod] 14 | public void TestAccountActor() 15 | { 16 | var system = ActorSystem.Create("acountContext"); 17 | 18 | var accountService = system.ActorOf(); 19 | accountService.Tell(new ManageAccount()); 20 | 21 | Thread.Sleep(5000); 22 | } 23 | } 24 | 25 | public class ManageAccount { } 26 | 27 | public class AccountService : TypedActor, 28 | IHandle, 29 | IHandle>, 30 | IHandle>, 31 | IHandle> 32 | { 33 | private IActorRef account = null; 34 | 35 | public void Handle(ManageAccount message) 36 | { 37 | account = Context.ActorOf("A-1234"); 38 | account.Tell(new OpenAccount("A-1234", 100)); 39 | } 40 | 41 | public void Handle(Tuple message) 42 | { 43 | Console.WriteLine("AccountOpened: State: {0} and Event: {1}", message.Item1, message.Item2); 44 | account.Tell(new DepositFunds(50)); 45 | } 46 | 47 | public void Handle(Tuple message) 48 | { 49 | Console.WriteLine("FundsDeposited: State: {0} and Event: {1}", message.Item1, message.Item2); 50 | account.Tell(new WithdrawFunds(75)); 51 | } 52 | 53 | public void Handle(Tuple message) 54 | { 55 | Console.WriteLine("FundsWithdrawn: State: {0} and Event: {1}", message.Item1, message.Item2); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DDDwithActors.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaughnVernon/DDDwithActors/35e3ff26550e8d7f1c1c8bfbcefa4c8a707b3288/DDDwithActors.pdf -------------------------------------------------------------------------------- /Elixir/Advanced/account.ex: -------------------------------------------------------------------------------- 1 | defmodule Account do 2 | defmodule State do 3 | defstruct account_number: nil, balance: nil 4 | end 5 | 6 | defmodule Commands do 7 | defmodule OpenAccount do 8 | defstruct account_number: nil, initial_balance: nil 9 | end 10 | 11 | defmodule DepositFunds do 12 | defstruct amount: nil 13 | end 14 | 15 | defmodule WithdrawFunds do 16 | defstruct amount: nil 17 | end 18 | end 19 | 20 | alias Commands.{OpenAccount, DepositFunds, WithdrawFunds} 21 | 22 | defmodule Events do 23 | defmodule AccountOpened do 24 | @derive {Inspect, only: [:account_number, :initial_balance]} 25 | defstruct account_number: nil, initial_balance: nil 26 | end 27 | 28 | defmodule FundsDeposited do 29 | @derive {Inspect, only: [:account_number, :amount, :new_balance]} 30 | defstruct account_number: nil, amount: nil, new_balance: nil 31 | end 32 | 33 | defmodule FundsWithdrawn do 34 | @derive {Inspect, only: [:account_number, :amount, :new_balance]} 35 | defstruct account_number: nil, amount: nil, new_balance: nil 36 | end 37 | end 38 | 39 | alias Events.{AccountOpened, FundsDeposited, FundsWithdrawn} 40 | 41 | use GenServer 42 | 43 | # Client (Public API) 44 | 45 | def start_link(init_arg \\ nil) do 46 | GenServer.start_link(__MODULE__, init_arg) 47 | end 48 | 49 | def open(pid, fields) do 50 | GenServer.call(pid, {:handle, struct!(OpenAccount, fields)}) 51 | end 52 | 53 | def deposit(pid, fields) do 54 | GenServer.call(pid, {:handle, struct!(DepositFunds, fields)}) 55 | end 56 | 57 | def withdraw(pid, fields) do 58 | GenServer.call(pid, {:handle, struct!(WithdrawFunds, fields)}) 59 | end 60 | 61 | # Server (process implementation) 62 | 63 | def init(_) do 64 | {:ok, %State{}} 65 | end 66 | 67 | def handle_call({:handle, command}, _from, state) do 68 | case handle_command(command, state) do 69 | {:error, reason} -> 70 | {:reply, {:error, reason}, state} 71 | 72 | handle_result -> 73 | emitted_events = List.wrap(handle_result) 74 | new_state = Enum.reduce(emitted_events, state, &apply_event/2) 75 | {:reply, {:ok, emitted_events}, new_state} 76 | end 77 | end 78 | 79 | def handle_command(%OpenAccount{} = open, %State{}) do 80 | %AccountOpened{ 81 | account_number: open.account_number, 82 | initial_balance: open.initial_balance 83 | } 84 | end 85 | 86 | def handle_command(%DepositFunds{} = deposit, %State{} = state) do 87 | %FundsDeposited{ 88 | account_number: state.account_number, 89 | amount: deposit.amount, 90 | new_balance: state.balance + deposit.amount 91 | } 92 | end 93 | 94 | def handle_command(%WithdrawFunds{} = withdraw, %State{} = state) do 95 | event = %FundsWithdrawn{ 96 | account_number: state.account_number, 97 | amount: withdraw.amount, 98 | new_balance: state.balance - withdraw.amount 99 | } 100 | 101 | cond do 102 | event.new_balance < 0 -> 103 | {:error, :insufficient_funds} 104 | 105 | :otherwise -> 106 | event 107 | end 108 | end 109 | 110 | def apply_event(%AccountOpened{} = opened, %State{} = state) do 111 | %State{state | account_number: opened.account_number, balance: opened.initial_balance} 112 | end 113 | 114 | def apply_event(%FundsDeposited{} = deposited, %State{} = state) do 115 | %State{state | balance: state.balance + deposited.amount} 116 | end 117 | 118 | def apply_event(%FundsWithdrawn{} = withdrawn, %State{} = state) do 119 | %State{state | balance: state.balance - withdrawn.amount} 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /Elixir/Advanced/account_run.exs: -------------------------------------------------------------------------------- 1 | with {:ok, account} <- Account.start_link(), 2 | {:ok, open_events} <- Account.open(account, account_number: "A-1234", initial_balance: 100), 3 | {:ok, deposit_events} <- Account.deposit(account, amount: 50), 4 | {:ok, withdraw_events} <- Account.withdraw(account, amount: 75) do 5 | [open_events, deposit_events, withdraw_events] 6 | |> List.flatten() 7 | |> Enum.each(&IO.inspect/1) 8 | end 9 | -------------------------------------------------------------------------------- /Elixir/Advanced/account_test.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(autorun: false) 2 | 3 | defmodule AccountTest do 4 | use ExUnit.Case, async: true 5 | 6 | Code.require_file("account.ex") 7 | 8 | setup :start_supervised_account 9 | 10 | test "account_run.exs works" do 11 | import ExUnit.CaptureIO 12 | 13 | execute_script = fn -> 14 | Code.require_file("account_run.exs") 15 | end 16 | 17 | assert capture_io(execute_script) =~ """ 18 | %Account.Events.AccountOpened{account_number: \"A-1234\", initial_balance: 100} 19 | %Account.Events.FundsDeposited{ 20 | account_number: \"A-1234\", 21 | amount: 50, 22 | new_balance: 150 23 | } 24 | %Account.Events.FundsWithdrawn{ 25 | account_number: \"A-1234\", 26 | amount: 75, 27 | new_balance: 75 28 | } 29 | """ 30 | end 31 | 32 | test "opening account", %{account_pid: pid} do 33 | {:ok, [opened]} = Account.open(pid, account_number: "A-1234", initial_balance: 100) 34 | assert %Account.Events.AccountOpened{account_number: "A-1234", initial_balance: 100} = opened 35 | end 36 | 37 | test "depositing funds", %{account_pid: pid} do 38 | {:ok, _} = Account.open(pid, account_number: "A-1234", initial_balance: 100) 39 | {:ok, [deposited]} = Account.deposit(pid, amount: 50) 40 | assert %Account.Events.FundsDeposited{amount: 50} = deposited 41 | end 42 | 43 | test "withdrawing funds", %{account_pid: pid} do 44 | {:ok, _} = Account.open(pid, account_number: "A-1234", initial_balance: 100) 45 | {:ok, _} = Account.deposit(pid, amount: 50) 46 | {:ok, [withdrawn]} = Account.withdraw(pid, amount: 75) 47 | assert %Account.Events.FundsWithdrawn{amount: 75} = withdrawn 48 | end 49 | 50 | test "attempting to withdraw from empty account", %{account_pid: pid} do 51 | {:ok, _} = Account.open(pid, account_number: "A-1234", initial_balance: 0) 52 | assert {:error, :insufficient_funds} = Account.withdraw(pid, amount: 50) 53 | end 54 | 55 | def start_supervised_account(_ctx) do 56 | [account_pid: start_supervised!(Account)] 57 | end 58 | end 59 | 60 | ExUnit.run() 61 | -------------------------------------------------------------------------------- /Elixir/account.ex: -------------------------------------------------------------------------------- 1 | defmodule Account do 2 | 3 | defmodule State do 4 | defstruct account_number: nil, balance: nil 5 | defimpl String.Chars, for: State do 6 | def to_string(state), do: "Account.State account_number: #{state.account_number}, balance: #{state.balance}" 7 | end 8 | end 9 | 10 | defmodule Commands do 11 | defmodule OpenAccount do 12 | defstruct account_number: nil, initial_balance: nil 13 | end 14 | defmodule DepositFunds do 15 | defstruct amount: nil 16 | end 17 | defmodule WithdrawFunds do 18 | defstruct amount: nil 19 | end 20 | end 21 | 22 | alias Commands.{OpenAccount,DepositFunds,WithdrawFunds} 23 | 24 | defmodule Events do 25 | defmodule AccountOpened do 26 | defstruct account_number: nil, initial_balance: nil 27 | end 28 | defimpl String.Chars, for: AccountOpened do 29 | def to_string(event), do: "Account.AccountOpened account_number: #{event.account_number}, initial_balance: #{event.initial_balance}" 30 | end 31 | defmodule FundsDeposited do 32 | defstruct amount: nil, balance: nil 33 | end 34 | defimpl String.Chars, for: FundsDeposited do 35 | def to_string(event), do: "Account.FundsDeposited amount: #{event.amount}, balance: #{event.balance}" 36 | end 37 | defmodule FundsWithdrawn do 38 | defstruct amount: nil, balance: nil 39 | end 40 | defimpl String.Chars, for: FundsWithdrawn do 41 | def to_string(event), do: "Account.FundsWithdrawn amount: #{event.amount}, balance: #{event.balance}" 42 | end 43 | end 44 | 45 | alias Events.{AccountOpened,FundsDeposited,FundsWithdrawn} 46 | 47 | def start do 48 | spawn(__MODULE__, :process, [%State{}]) 49 | end 50 | 51 | def process(state) do 52 | receive do 53 | {sender, command} -> 54 | {newState, event} = handle(state, command) 55 | send sender, {newState, event} 56 | process(newState) 57 | end 58 | end 59 | 60 | def handle(%State{} = state, %OpenAccount{} = open) do 61 | state = %State{state | account_number: open.account_number, balance: open.initial_balance} 62 | event = %AccountOpened{account_number: open.account_number, initial_balance: open.initial_balance} 63 | {state, event} 64 | end 65 | 66 | def handle(%State{} = state, %DepositFunds{} = deposit) do 67 | state = %State{state | balance: state.balance + deposit.amount} 68 | event = %FundsDeposited{amount: deposit.amount, balance: state.balance} 69 | {state, event} 70 | end 71 | 72 | def handle(%State{} = state, %WithdrawFunds{} = withdraw) do 73 | state = %State{state | balance: state.balance - withdraw.amount} 74 | event = %FundsWithdrawn{amount: withdraw.amount, balance: state.balance} 75 | {state, event} 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /Elixir/account_run.exs: -------------------------------------------------------------------------------- 1 | defmodule Receiver do 2 | def receive_result do 3 | receive do 4 | {state, event} -> 5 | IO.puts event 6 | IO.puts state 7 | state 8 | end 9 | end 10 | end 11 | 12 | account = Account.start 13 | 14 | send account, {self(), %Account.Commands.OpenAccount{account_number: "A-1234", initial_balance: 100}} 15 | 16 | Receiver.receive_result 17 | 18 | send account, {self(), %Account.Commands.DepositFunds{amount: 50}} 19 | 20 | Receiver.receive_result 21 | 22 | send account, {self(), %Account.Commands.WithdrawFunds{amount: 75}} 23 | 24 | Receiver.receive_result 25 | -------------------------------------------------------------------------------- /Erlang/account.erl: -------------------------------------------------------------------------------- 1 | -module(account). 2 | -export([start/0, process/1]). 3 | -include("account_record.hrl"). 4 | 5 | start() -> 6 | spawn(account, process, [#state{}]). 7 | 8 | process(State = #state{}) -> 9 | receive 10 | {Sender, Command} -> 11 | {NewState, Event} = handle(State, Command), 12 | Sender ! {NewState, Event}, 13 | process(NewState); 14 | U = _ -> 15 | io:format("Unknown command: ~p~n", [U]), 16 | process(State) 17 | end. 18 | 19 | handle(State = #state{}, Command = #open_account{}) -> 20 | NewState = State#state{account_number=Command#open_account.account_number, balance=Command#open_account.initial_balance}, 21 | Event = #account_opened{account_number=Command#open_account.account_number, initial_balance=Command#open_account.initial_balance}, 22 | {NewState, Event}; 23 | handle(State = #state{}, Command = #deposit_funds{}) -> 24 | NewState = State#state{balance = State#state.balance + Command#deposit_funds.amount}, 25 | Event = #funds_deposited{amount = Command#deposit_funds.amount, balance = NewState#state.balance}, 26 | {NewState, Event}; 27 | handle(State = #state{}, Command = #withdraw_funds{}) -> 28 | NewState = State#state{balance = State#state.balance - Command#withdraw_funds.amount}, 29 | Event = #funds_withdrawn{amount = Command#withdraw_funds.amount, balance = NewState#state.balance}, 30 | {NewState, Event}. 31 | -------------------------------------------------------------------------------- /Erlang/account_record.hrl: -------------------------------------------------------------------------------- 1 | -record(state, {account_number, balance}). 2 | 3 | -record(open_account, {account_number, initial_balance}). 4 | -record(deposit_funds, {amount}). 5 | -record(withdraw_funds, {amount}). 6 | 7 | -record(account_opened, {account_number, initial_balance}). 8 | -record(funds_deposited, {amount, balance}). 9 | -record(funds_withdrawn, {amount, balance}). 10 | 11 | -record(error, {reason}). 12 | -------------------------------------------------------------------------------- /Erlang/account_runner.erl: -------------------------------------------------------------------------------- 1 | -module(account_runner). 2 | -export([run1/0]). 3 | -import(account, [start/0, process/0]). 4 | -include("account_record.hrl"). 5 | 6 | receive_result() -> 7 | receive 8 | {State, Event} -> 9 | io:format("Event: ~p~n", [Event]), 10 | io:format("State: ~p~n", [State]), 11 | State 12 | end. 13 | 14 | run1() -> 15 | Self = self(), 16 | Account = account:start(), 17 | Account ! {Self, #open_account{account_number="A-1234", initial_balance=100}}, 18 | receive_result(), 19 | Account ! {Self, #deposit_funds{amount=50}}, 20 | receive_result(), 21 | Account ! {Self, #withdraw_funds{amount=75}}, 22 | receive_result(). 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DDDwithActors 2 | Note that at the time this workshop was given at DDD Europe 2017, Akka.NET did not fully support the Akka Persistence specification/implementation. Thus, I didn't provide the Account.cs sample as extending PersistentActor. You can look at the Scala/Akka example to see how this might work once supported. 3 | 4 | ## Slide Deck 5 | This is the slide deck from the "DDD with Actors" mini-workshop. 6 | 7 | - [Slide Deck](https://github.com/VaughnVernon/DDDwithActors/blob/master/DDDwithActors.pdf) 8 | 9 | ## Examples 10 | There are examples in various programming languages and using different toolkits. 11 | 12 | - [C# with Akka.NET](https://github.com/VaughnVernon/DDDwithActors/tree/master/Akka.NET) 13 | 14 | - [Elixir](https://github.com/VaughnVernon/DDDwithActors/tree/master/Elixir) 15 | 16 | - [Erlang](https://github.com/VaughnVernon/DDDwithActors/tree/master/Erlang) 17 | 18 | - [Pony](https://github.com/d-led/DDDwithActorsPony) provided by [Dmitry Ledentsov](https://github.com/d-led) 19 | 20 | - [Scala/Akka](https://github.com/VaughnVernon/DDDwithActors/tree/master/ScalaAkka) 21 | -------------------------------------------------------------------------------- /ScalaAkka/build.sbt: -------------------------------------------------------------------------------- 1 | name := """AccountContext""" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.12.1" 6 | 7 | libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.4.16" 8 | libraryDependencies += "com.typesafe.akka" %% "akka-persistence" % "2.4.16" 9 | libraryDependencies += "com.github.dnvriend" %% "akka-persistence-inmemory" % "1.3.18" 10 | 11 | resolvers += "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases" 12 | resolvers += Resolver.jcenterRepo 13 | -------------------------------------------------------------------------------- /ScalaAkka/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor { 3 | serializers { 4 | } 5 | serialization-bindings { 6 | } 7 | 8 | warn-about-java-serializer-usage = false 9 | } 10 | 11 | persistence { 12 | journal.plugin = "inmemory-journal" 13 | snapshot-store.plugin = "inmemory-snapshot-store" 14 | } 15 | } 16 | 17 | inmemory-journal { 18 | write-plugin = "inmemory-journal" 19 | offset-mode = "sequence" 20 | ask-timeout = "10s" 21 | refresh-interval = "100ms" 22 | max-buffer-size = "100" 23 | 24 | event-adapters { 25 | } 26 | 27 | event-adapter-bindings { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ScalaAkka/src/main/scala/Account.scala: -------------------------------------------------------------------------------- 1 | import akka.persistence.PersistentActor 2 | 3 | case class OpenAccount(accountNumber: String, initialBalance: Int) 4 | case class DepositFunds(amount: Int) 5 | case class WithdrawFunds(amount: Int) 6 | 7 | case class AccountOpened(accountNumber: String, initialBalance: Int) 8 | case class FundsDeposited(accountNumber: String, amount: Int) 9 | case class FundsWithdrawn(accountNumber: String, amount: Int) 10 | 11 | class Account extends PersistentActor { 12 | var state: Option[AccountState] = None 13 | 14 | override def persistenceId: String = self.path.name 15 | 16 | override def receiveCommand: Receive = { 17 | case open: OpenAccount => 18 | persist(AccountOpened(open.accountNumber, open.initialBalance)) { event => 19 | updateWith(event) 20 | sender ! (state, event) 21 | } 22 | case deposit: DepositFunds => 23 | persist(FundsDeposited(accountNumber, deposit.amount)) { event => 24 | updateWith(event) 25 | sender ! (state, event) 26 | } 27 | case withdraw: WithdrawFunds => 28 | persist(FundsWithdrawn(accountNumber, withdraw.amount)) { event => 29 | updateWith(event) 30 | sender ! (state, event) 31 | } 32 | } 33 | 34 | override def receiveRecover: Receive = { 35 | case event: AccountOpened => updateWith(event) 36 | case event: FundsDeposited => updateWith(event) 37 | case event: FundsWithdrawn => updateWith(event) 38 | } 39 | 40 | def accountNumber: String = 41 | state.getOrElse(AccountState("unknown", 0)).accountNumber 42 | 43 | def updateWith(event: AccountOpened): Unit = 44 | state = Option(AccountState(event.accountNumber, event.initialBalance)) 45 | 46 | def updateWith(event: FundsDeposited): Unit = 47 | state = state.map(_.fromDeposited(event.amount)) 48 | 49 | def updateWith(event: FundsWithdrawn): Unit = 50 | state = state.map(_.fromWithdrawn(event.amount)) 51 | } 52 | 53 | case class AccountState(accountNumber: String, balance: Int) { 54 | def fromDeposited(amount: Int): AccountState = 55 | AccountState(accountNumber, balance + amount) 56 | 57 | def fromWithdrawn(amount: Int): AccountState = 58 | AccountState(accountNumber, balance - amount) 59 | } 60 | -------------------------------------------------------------------------------- /ScalaAkka/src/main/scala/AccountRunner.scala: -------------------------------------------------------------------------------- 1 | import akka.actor._ 2 | 3 | import com.typesafe.config._ 4 | 5 | object AccountRunner extends App { 6 | val system = ActorSystem("accountsContext") 7 | val accountService = system.actorOf(Props[AccountService], "accountService") 8 | accountService ! ManageAccount("A-1234") 9 | 10 | Thread.sleep(2000L) 11 | } 12 | 13 | case class ManageAccount(accountNumber: String) 14 | 15 | class AccountService extends Actor { 16 | var account: ActorRef = _ 17 | 18 | def receive = { 19 | case manage: ManageAccount => 20 | account = context.actorOf(Props[Account], manage.accountNumber) 21 | account ! OpenAccount(manage.accountNumber, 100) 22 | case (state, event: AccountOpened) => 23 | println(s"Event: $event") 24 | println(s"State: $state") 25 | account ! DepositFunds(50) 26 | case (state, event: FundsDeposited) => 27 | println(s"Event: $event") 28 | println(s"State: $state") 29 | account ! WithdrawFunds(75) 30 | case (state, event: FundsWithdrawn) => 31 | println(s"Event: $event") 32 | println(s"State: $state") 33 | } 34 | } 35 | --------------------------------------------------------------------------------