From f2ba5f19db085ebca427d8c07f200eb1aa5a18fa Mon Sep 17 00:00:00 2001 From: Lutando Ngqakaza Date: Sun, 13 May 2018 17:52:45 +0200 Subject: [PATCH 1/7] added readmodel, reposity and query model for walkthrough --- .../.idea.Akkatecture/.idea/contentModel.xml | 14 +++++++ .vscode/launch.json | 38 +++++++++++++++++++ .vscode/tasks.json | 15 ++++++++ .../Program.cs | 25 ++++++++++-- .../Model/Account/ValueObjects/Money.cs | 5 +++ .../Revenue/Commands/AddRevenueCommand.cs | 14 +++++++ .../Revenue/Queries/GetRevenueQuery.cs | 7 ++++ .../Revenue/ReadModels/RevenueReadModel.cs | 16 ++++++++ .../Repositories/Revenue/RevenueRepository.cs | 34 +++++++++++++++++ .../Subscribers/RevenueSubscriber.cs | 12 +++++- 10 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Commands/AddRevenueCommand.cs create mode 100644 examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Queries/GetRevenueQuery.cs create mode 100644 examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/ReadModels/RevenueReadModel.cs create mode 100644 examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/RevenueRepository.cs diff --git a/.idea/.idea.Akkatecture/.idea/contentModel.xml b/.idea/.idea.Akkatecture/.idea/contentModel.xml index dd0cc0de..475c7f32 100644 --- a/.idea/.idea.Akkatecture/.idea/contentModel.xml +++ b/.idea/.idea.Akkatecture/.idea/contentModel.xml @@ -117,6 +117,20 @@ + + + + + + + + + + + + + + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..e60bc78e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Akkatecture.Examples.UserAccount.Application", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/examples/simple/Akkatecture.Examples.UserAccount.Application/bin/Debug/netcoreapp2.0/Akkatecture.Examples.UserAccount.Application.dll", + "args": [], + "cwd": "${workspaceFolder}/examples/simple/Akkatecture.Examples.UserAccount.Application", + "console": "integratedTerminal", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": "Akkatecture.Walkthrough.Application", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/examples/walkthrough/Akkatecture.Walkthrough.Application/bin/Debug/netcoreapp2.0/Akkatecture.Walkthrough.Application.dll", + "args": [], + "cwd": "${workspaceFolder}/examples/walkthrough/Akkatecture.Walkthrough.Application", + "console": "integratedTerminal", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..2c992f46 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/examples/cluster/Akkatecture.Examples.ClusterClient/Akkatecture.Examples.ClusterClient.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs b/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs index 7cc5f406..56553610 100644 --- a/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs +++ b/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs @@ -1,10 +1,14 @@ using System; using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; using Akka.Actor; using Akkatecture.Walkthrough.Domain.Model.Account; using Akkatecture.Walkthrough.Domain.Model.Account.Commands; using Akkatecture.Walkthrough.Domain.Model.Account.Entities; using Akkatecture.Walkthrough.Domain.Model.Account.ValueObjects; +using Akkatecture.Walkthrough.Domain.Repositories.Revenue; +using Akkatecture.Walkthrough.Domain.Repositories.Revenue.Queries; +using Akkatecture.Walkthrough.Domain.Repositories.Revenue.ReadModels; using Akkatecture.Walkthrough.Domain.Sagas.MoneyTransfer; using Akkatecture.Walkthrough.Domain.Subscribers; @@ -13,19 +17,23 @@ namespace Akkatecture.Walkthrough.Application public class Program { public static IActorRef AccountManager { get; set; } + public static IActorRef RevenueRepository { get; set; } public static void CreateActorSystem() { //Create actor system var system = ActorSystem.Create("bank-system"); - var aggregateManager = system.ActorOf(Props.Create(() => new AccountManager())); + var aggregateManager = system.ActorOf(Props.Create(() => new AccountManager()),"account-manager"); + + //Create revenue repository + RevenueRepository = system.ActorOf(Props.Create(() => new RevenueRepository()),"revenue-repository"); //Create subscriber for revenue - system.ActorOf(Props.Create(() => new RevenueSubscriber())); + system.ActorOf(Props.Create(() => new RevenueSubscriber(RevenueRepository)),"revenue-subscriber"); //Create saga manager system.ActorOf(Props.Create(() => - new MoneyTransferSagaManager(() => new MoneyTransferSaga(aggregateManager)))); + new MoneyTransferSagaManager(() => new MoneyTransferSaga(aggregateManager))),"moneytransfer-saga"); AccountManager = aggregateManager; } @@ -52,7 +60,18 @@ public static void Main(string[] args) AccountManager.Tell(transferMoneyCommand); + Task.Delay(TimeSpan.FromSeconds(1)).Wait(); + Console.WriteLine("Walkthrough operations complete.\n\n"); + Console.WriteLine("Press Enter to get the revenue:"); + Console.ReadLine(); + + var revenue = RevenueRepository.Ask(new GetRevenueQuery(), TimeSpan.FromMilliseconds(500)).Result; + + Console.WriteLine($"The Revenue is: {revenue.Revenue.Value}."); + Console.WriteLine($"From: {revenue.Transactions} transaction(s)."); + + Console.ReadLine(); } } } diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Model/Account/ValueObjects/Money.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Model/Account/ValueObjects/Money.cs index c90b1060..b67905e4 100644 --- a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Model/Account/ValueObjects/Money.cs +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Model/Account/ValueObjects/Money.cs @@ -22,5 +22,10 @@ public Money(decimal value) { return new Money(m1.Value - m2.Value); } + + public override string ToString() + { + return Value.ToString(); + } } } \ No newline at end of file diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Commands/AddRevenueCommand.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Commands/AddRevenueCommand.cs new file mode 100644 index 00000000..a484511e --- /dev/null +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Commands/AddRevenueCommand.cs @@ -0,0 +1,14 @@ +using Akkatecture.Walkthrough.Domain.Model.Account.ValueObjects; + +namespace Akkatecture.Walkthrough.Domain.Repositories.Revenue.Commands +{ + public class AddRevenueCommand + { + public Money AmountToAdd { get; } + + public AddRevenueCommand(Money amountToAdd) + { + AmountToAdd = amountToAdd; + } + } +} \ No newline at end of file diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Queries/GetRevenueQuery.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Queries/GetRevenueQuery.cs new file mode 100644 index 00000000..1ef48852 --- /dev/null +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/Queries/GetRevenueQuery.cs @@ -0,0 +1,7 @@ +namespace Akkatecture.Walkthrough.Domain.Repositories.Revenue.Queries +{ + public class GetRevenueQuery + { + + } +} \ No newline at end of file diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/ReadModels/RevenueReadModel.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/ReadModels/RevenueReadModel.cs new file mode 100644 index 00000000..70408c87 --- /dev/null +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/ReadModels/RevenueReadModel.cs @@ -0,0 +1,16 @@ +using Akkatecture.Walkthrough.Domain.Model.Account.ValueObjects; + +namespace Akkatecture.Walkthrough.Domain.Repositories.Revenue.ReadModels +{ + public class RevenueReadModel + { + public Money Revenue { get; } + public int Transactions { get; } + + public RevenueReadModel(Money revenue, int transactions) + { + Revenue = revenue; + Transactions = transactions; + } + } +} \ No newline at end of file diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/RevenueRepository.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/RevenueRepository.cs new file mode 100644 index 00000000..7766e6ca --- /dev/null +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Repositories/Revenue/RevenueRepository.cs @@ -0,0 +1,34 @@ +using Akka.Actor; +using Akkatecture.Walkthrough.Domain.Model.Account.ValueObjects; +using Akkatecture.Walkthrough.Domain.Repositories.Revenue.Commands; +using Akkatecture.Walkthrough.Domain.Repositories.Revenue.Queries; +using Akkatecture.Walkthrough.Domain.Repositories.Revenue.ReadModels; + +namespace Akkatecture.Walkthrough.Domain.Repositories.Revenue +{ + public class RevenueRepository : ReceiveActor + { + public Money Revenue { get; private set; } = new Money(0.00m); + public int Transactions { get; private set; } = 0; + + public RevenueRepository() + { + Receive(Handle); + Receive(Handle); + } + + private bool Handle(AddRevenueCommand command) + { + Revenue = Revenue + command.AmountToAdd; + Transactions++; + return true; + } + + private bool Handle(GetRevenueQuery query) + { + var readModel = new RevenueReadModel(Revenue, Transactions); + Sender.Tell(readModel); + return true; + } + } +} \ No newline at end of file diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs index 5812ec10..3bebdba0 100644 --- a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs @@ -1,20 +1,28 @@ using System.Threading.Tasks; +using Akka.Actor; using Akkatecture.Aggregates; using Akkatecture.Subscribers; using Akkatecture.Walkthrough.Domain.Model.Account; using Akkatecture.Walkthrough.Domain.Model.Account.Events; using Akkatecture.Walkthrough.Domain.Model.Account.ValueObjects; +using Akkatecture.Walkthrough.Domain.Repositories.Revenue.Commands; namespace Akkatecture.Walkthrough.Domain.Subscribers { public class RevenueSubscriber : DomainEventSubscriber, ISubscribeTo { - public Money Revenue { get; private set; } = new Money(0m); + public IActorRef RevenueRepository { get; } + + public RevenueSubscriber(IActorRef revenueRepository) + { + RevenueRepository = revenueRepository; + } public Task Handle(IDomainEvent domainEvent) { - Revenue += domainEvent.AggregateEvent.Amount; + var command = new AddRevenueCommand(domainEvent.AggregateEvent.Amount); + RevenueRepository.Tell(command); return Task.CompletedTask; } From 3be1d300884c51cd31b1544de2071a752075c2f7 Mon Sep 17 00:00:00 2001 From: Lutando Ngqakaza Date: Sun, 13 May 2018 17:54:29 +0200 Subject: [PATCH 2/7] appveyor changes --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index eb8696fe..71ab9957 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,7 @@ version: 0.0.{build} +branches: + only: + - master skip_tags: true image: Visual Studio 2017 configuration: Release From 14442238f73531101686604b6cfd658d2c65667b Mon Sep 17 00:00:00 2001 From: Lutando Ngqakaza Date: Sun, 13 May 2018 22:36:13 +0200 Subject: [PATCH 3/7] added run configurations for rider and vscode --- .vscode/launch.json | 42 +++++++++++++++++-- README.md | 20 +++++---- examples/cluster/README.md | 2 + examples/simple/README.md | 4 +- examples/walkthrough/README.md | 2 + src/Akkatecture/Aggregates/AggregateRoot.cs | 10 +++++ .../Sagas/AggregateSaga/AggregateSaga.cs | 6 +++ 7 files changed, 73 insertions(+), 13 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e60bc78e..1550599d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,10 +29,46 @@ "internalConsoleOptions": "openOnSessionStart" }, { - "name": ".NET Core Attach", + "name": "Akkatecture.Examples.ClusterClient", "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/examples/cluster/Akkatecture.Examples.ClusterClient/bin/Debug/netcoreapp2.0/Akkatecture.Examples.ClusterClient.dll", + "args": [], + "cwd": "${workspaceFolder}/examples/cluster/Akkatecture.Examples.ClusterClient", + "console": "integratedTerminal", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": "Akkatecture.Examples.Seed", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/examples/cluster/Akkatecture.Examples.Seed/bin/Debug/netcoreapp2.0/Akkatecture.Examples.Seed.dll", + "args": [], + "cwd": "${workspaceFolder}/examples/cluster/Akkatecture.Examples.Seed", + "console": "integratedTerminal", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": "Akkatecture.Examples.Worker", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/examples/cluster/Akkatecture.Examples.Worker/bin/Debug/netcoreapp2.0/Akkatecture.Examples.Worker.dll", + "args": [], + "cwd": "${workspaceFolder}/examples/cluster/Akkatecture.Examples.Worker", + "console": "integratedTerminal", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" } + ], + "compounds": [ + { + "name": "Akkatecture.Examples.SeedWorkerCluster", + "configurations": ["Akkatecture.Examples.Seed", "Akkatecture.Examples.Worker", "Akkatecture.Examples.ClusterClient"] + } ] } \ No newline at end of file diff --git a/README.md b/README.md index b0838399..257aa80c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ + + [![Build Status](https://travis-ci.org/Lutando/Akkatecture.svg?branch=master)](https://travis-ci.org/Lutando/Akkatecture) [![Build status](https://ci.appveyor.com/api/projects/status/cxdg8eyk7d5nmqgj?svg=true)](https://ci.appveyor.com/project/Lutando/akkatecture) [![Nuget Package](https://img.shields.io/nuget/v/Akkatecture.svg?style=flat)](https://www.nuget.org/packages/Akkatecture/) @@ -16,11 +18,11 @@ Go ahead and take a look at [our documentation](http://akkatecture.net/docs/gett ### Features -* **Distributed:** Backed by akka.net's actor system. Akkatecture enjoys the benefits of a reasonable distributed computing computation model. -* **Message based:** For high levels of message throughput and processing Akkatecture [does not ask, it tells](http://bartoszsypytkowski.com/dont-ask-tell-2/). +* **Distributed:** Backed by akka.net's actor model. Akkatecture enjoys the benefits of a good distributed computing computation model. +* **Message based:** For high levels of processing throughput Akkatecture [does not ask, it tells](http://bartoszsypytkowski.com/dont-ask-tell-2/). * **Event sourced:** By design, aggregate roots derive their state by replaying persisted events. -* **Highly scalable:** Actors with their thread safe and distributed nature gives us this plus point. -* **Configurable:** Through akka.net's hocon configuration. +* **Highly scalable:** Work proceeds interactively and concurrently, overlapping in time. +* **Configurable:** Through akka.net's hocon configuration, you will be able to configure every aspect of your application. ### Examples @@ -33,7 +35,7 @@ Akkatecture comes with a few examples on how to best use it: * **[Cluster](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster):** A more involved sample that shows you how to do distributed aggregates using clustering. Read the [readme](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster/README.md) for the sample for a good overview of the example. -* **[Test Example](https://github.com/Lutando/Akkatecture/tree/master/test/Akkatecture.TestHelpers/Aggregates):** The test examples found in the Akkatecture.TestHelpers project is there to provide assistance when doing testing for Akkatecture. There is a simple domain modelled that includes an aggregate with a simple aggregate saga, and these are used to do simple black box style testing on Akkatecture using akka.net's TestKit. +* **[Tests](https://github.com/Lutando/Akkatecture/tree/master/test/Akkatecture.TestHelpers/Aggregates):** The test examples found in the Akkatecture.TestHelpers project is there to provide assistance when doing testing for Akkatecture. There is a simple domain modelled that includes an aggregate with a simple aggregate saga, and these are used to do simple black box style testing on Akkatecture using akka.net's TestKit. **Note:** This example is part of the Akkatecture simple example project, so checkout [the @@ -57,10 +59,10 @@ var changeNameCommand = new UserAccountChangeNameCommand(aggregateId, "foo bar b aggregateManager.Tell(changeNameCommand); ``` -### Assumptions About Akkatecture Users +### Assumptions About Akkatecture Developers -You should have a comfortable grasp of domain driven design, cqrs, and event sourcing concepts. -It would also be benefitial for you to be familiar with actor systems, akka.net, and the extensibility points that akka gives you through hocon configuration. +You should have some expirience in domain driven design, cqrs, and event sourcing. +It would also be benefitial for you to be familiar with actor systems, akka.net, and the extensibility points that akka gives you through hocon configuration. However if you follow the walkthrough on the documentation website you will be acquinted with many akka.net concepts. ### Status of Akkatecture @@ -109,7 +111,7 @@ Doing domain driven design in a distributed scenario is quite tricky. And even m Akkatecture gives you a set of semi-opinionated generic constructs that you can use to wire up your application so that you can focus on your main task, modelling and codifying your business domain. -Akka.net gives us a wealth of good APIs out of the box that can be used to build entire systems out of. It also has a decent ecosystem and community for support. I also am of the opinion that commands translate well semantically in actor systems since you are telling the actor what you want, and the actor might or might not "respond" with a fact or a bag of facts relating to what that command produced in context of that aggregate. +Akka.net gives us a wealth of good APIs out of the box that can be used to build entire systems out of. It also has a decent ecosystem and [community](https://gitter.im/akkadotnet/akka.net) for support. Akkatecture is also of the opinion that commands translate well semantically in actor systems since commands are telling the actor instance what your intent is, and then it is up to the actor to process the intent as it sees fit, this notion fits well into CQRS because that is basically what a command handler is for, it is to process command intent. ## Acknowledgements diff --git a/examples/cluster/README.md b/examples/cluster/README.md index 7aa59988..060846b4 100644 --- a/examples/cluster/README.md +++ b/examples/cluster/README.md @@ -21,3 +21,5 @@ This sample uses the same domain model as the one found in the [simple](https:// If you take a look at the hocon configurations for the [client](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster/Akkatecture.Examples.ClusterClient/client.conf), the [worker](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster/Akkatecture.Examples.Worker/worker.conf), and the [seed](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster/Akkatecture.Examples.Seed/seed.conf), you will see that they all point to the same well known address (127.0.0.1:6000). This address is the seed nodes address, it tells the actor systems to use that address to initiate cluster gossip, and thus to establish the clustered network of actor systems. Each of the projects use the default akkatecture hocon configuration as a fallback configuration because this configuration has akkatecture opinionated "sane" defaults. The sample is as easy as running all three projects at the same time. To interact with the domain just press enter on the clients console window, resulting in random commands being created, which are then fed through the cluster proxy that will serialize, deserialize, and route the commands to the necessary aggregate. To exit the applications safely, Press `Q` and enter. + +> To run the project in jetbrains rider or visual studio code, run the `Akkatecture.Examples.SeedWorkerCluster` configuration in the IDE. \ No newline at end of file diff --git a/examples/simple/README.md b/examples/simple/README.md index 14fce073..03ad3ad7 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -15,4 +15,6 @@ This is the domain of the sample which is a basic model of user accounts, it sup # Akkatecture.Examples.Application -This is the console application that creates the actor system and interfaces with it. First an aggregate manager is created, this aggregate manager is responsible for creating the aggregate actors beneath it, it is also responsible for supervising these actors and for routing messages to the appropriate aggregates. After this is done, a command for aggregate creation is made, and then the command is pushed through the aggregate manager, then a second command is done that pushed a change name command to the same aggregate that was previously created. Run the code to see how it works. \ No newline at end of file +This is the console application that creates the actor system and interfaces with it. First an aggregate manager is created, this aggregate manager is responsible for creating the aggregate actors beneath it, it is also responsible for supervising these actors and for routing messages to the appropriate aggregates. After this is done, a command for aggregate creation is made, and then the command is pushed through the aggregate manager, then a second command is done that pushed a change name command to the same aggregate that was previously created. Run the code to see how it works. + +> to run the application in jetbrains rider or visual studio code, run the `Akkatecture.Examples.UserAccount.Application` configuration in the IDE. \ No newline at end of file diff --git a/examples/walkthrough/README.md b/examples/walkthrough/README.md index 64114609..f24bb1d5 100644 --- a/examples/walkthrough/README.md +++ b/examples/walkthrough/README.md @@ -21,3 +21,5 @@ This is the domain of the sample which follows from the walkthrough tutorial. # Akkatecture.Walkthrough.Application + +> to run the application in jetbrains rider or visual studio code, run the `Akkatecture.Walkthrough.Application` configuration in the IDE. \ No newline at end of file diff --git a/src/Akkatecture/Aggregates/AggregateRoot.cs b/src/Akkatecture/Aggregates/AggregateRoot.cs index 54353160..af0948c4 100644 --- a/src/Akkatecture/Aggregates/AggregateRoot.cs +++ b/src/Akkatecture/Aggregates/AggregateRoot.cs @@ -63,8 +63,12 @@ protected AggregateRoot(TIdentity id) Register(State); if (Settings.UseDefaultEventRecover) + { Recover>>(Recover); Recover>(Recover); + Recover(Recover); + } + if (Settings.UseDefaultSnapshotRecover) Recover(Recover); @@ -312,6 +316,12 @@ protected virtual bool Recover(SnapshotOffer aggregateSnapshotOffer) return true; } + protected virtual bool Recover(RecoveryCompleted recoveryCompleted) + { + + return true; + } + private readonly Dictionary> _eventHandlers = new Dictionary>(); protected void Register(Action handler) where TAggregateEvent : IAggregateEvent diff --git a/src/Akkatecture/Sagas/AggregateSaga/AggregateSaga.cs b/src/Akkatecture/Sagas/AggregateSaga/AggregateSaga.cs index 1ee2aca1..25743668 100644 --- a/src/Akkatecture/Sagas/AggregateSaga/AggregateSaga.cs +++ b/src/Akkatecture/Sagas/AggregateSaga/AggregateSaga.cs @@ -120,6 +120,7 @@ protected AggregateSaga() { Recover>>(Recover); Recover>(Recover); + Recover(Recover); } @@ -315,6 +316,11 @@ protected virtual bool Recover(SnapshotOffer aggregateSnapshotOffer) return true; } + protected virtual bool Recover(RecoveryCompleted recoveryCompleted) + { + + return true; + } protected void SetSourceIdHistory(int count) { _previousSourceIds = new CircularBuffer(count); From 94cae070c2364cde1841be1fc86af17c9170ef10 Mon Sep 17 00:00:00 2001 From: Lutando Ngqakaza Date: Sun, 13 May 2018 22:45:20 +0200 Subject: [PATCH 4/7] changes to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 257aa80c..df1b2e6c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Go ahead and take a look at [our documentation](http://akkatecture.net/docs/gett ### Features * **Distributed:** Backed by akka.net's actor model. Akkatecture enjoys the benefits of a good distributed computing computation model. -* **Message based:** For high levels of processing throughput Akkatecture [does not ask, it tells](http://bartoszsypytkowski.com/dont-ask-tell-2/). +* **Message based:** Making it highly scalable by being reactive from message passing, Akkatecture [does not ask, it tells](http://bartoszsypytkowski.com/dont-ask-tell-2/). * **Event sourced:** By design, aggregate roots derive their state by replaying persisted events. * **Highly scalable:** Work proceeds interactively and concurrently, overlapping in time. * **Configurable:** Through akka.net's hocon configuration, you will be able to configure every aspect of your application. From 27567142954e30d48270721386555a207de007fd Mon Sep 17 00:00:00 2001 From: Lutando Ngqakaza Date: Mon, 14 May 2018 17:27:08 +0200 Subject: [PATCH 5/7] readme fixes and app fixes --- README.md | 1 + examples/cluster/README.md | 8 +++++--- examples/simple/README.md | 6 ++++++ .../Program.cs | 18 +++++++++++++++--- .../MoneyTransfer/MoneyTransferSagaLocator.cs | 4 +++- .../Subscribers/RevenueSubscriber.cs | 1 - examples/walkthrough/README.md | 18 ++++++++++++++---- 7 files changed, 44 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index df1b2e6c..8b91bd79 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Akkatecture is still in development. The goal of this projects first version is akkatecture is currently missing these crucial features: - aggregate state snapshotting. +- persisting event metadata. - typed actor references. ### Contributing diff --git a/examples/cluster/README.md b/examples/cluster/README.md index 060846b4..1dc5b1e4 100644 --- a/examples/cluster/README.md +++ b/examples/cluster/README.md @@ -2,15 +2,15 @@ The Cluster Sample is made up of three projects, `Akkatecture.Examples.ClusterClient`, `Akkatecture.Examples.Worker`, and `Akkatecture.Examples.Seed`. Here is a brief description of each project. -## [ClusterClient](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster/Akkatecture.Examples.ClusterClient) +## Akkatecture.Examples.ClusterClient The cluster client project is the code that interfaces with the cluster. It can be seen as the way to invoke the domain from an external place. It works by opening a proxy to the actual worker(s). All requests done through the client are transported / serialized / deserialized through the actor system and the requests are routed to the correct cluster shard that runs on the worker. -## [Worker](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster/Akkatecture.Examples.Worker) +## Akkatecture.Examples.Worker The worker is where all the real computation is done. The worker is responsible for routing commands to the right aggregates and the aggregates then process the commands, which may or may not result in events being emitted from them, resulting in those aggregate events being persisted to their event journal. In typical scenarios the worker would be deployed in numerous nodes within a network on the cloud or in a kubernetes cluster. -## [Seed](https://github.com/Lutando/Akkatecture/tree/master/examples/cluster/Akkatecture.Examples.Seed) +## Akkatecture.Examples.Seed The seed or seed node is only responsible for akka service discovery. Its only purpose is to exist at a well known location so that nodes within the actor system can effectively discover each other through the seed node. It is important that the seed does no work that can potentially disrupt its service level. In this sample the seeds address is 127.0.0.1:6000, the 10 workers are addressed from 127.0.0.1:6001-6011, and the console client is on 127.0.0.6100. @@ -22,4 +22,6 @@ If you take a look at the hocon configurations for the [client](https://github.c The sample is as easy as running all three projects at the same time. To interact with the domain just press enter on the clients console window, resulting in random commands being created, which are then fed through the cluster proxy that will serialize, deserialize, and route the commands to the necessary aggregate. To exit the applications safely, Press `Q` and enter. +The purpose of this example is to show how you can distribute your actorsystem using `Akka.Cluster`. Doing this does not require any changes to your domain code. Basically now your domain can be distributed across separate, networked nodes. + > To run the project in jetbrains rider or visual studio code, run the `Akkatecture.Examples.SeedWorkerCluster` configuration in the IDE. \ No newline at end of file diff --git a/examples/simple/README.md b/examples/simple/README.md index 03ad3ad7..716edaf8 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -17,4 +17,10 @@ This is the domain of the sample which is a basic model of user accounts, it sup This is the console application that creates the actor system and interfaces with it. First an aggregate manager is created, this aggregate manager is responsible for creating the aggregate actors beneath it, it is also responsible for supervising these actors and for routing messages to the appropriate aggregates. After this is done, a command for aggregate creation is made, and then the command is pushed through the aggregate manager, then a second command is done that pushed a change name command to the same aggregate that was previously created. Run the code to see how it works. +### Description + +In this sample, we instantiate the actor system and the various domain entities required for it to work. The domain entity required to +initialize this, is the `UserAccountAggregateManager`. We then interface with the domain by telling the manager to create user accounts +by instantiating and telling it a `CreateUserAccountCommand`. + > to run the application in jetbrains rider or visual studio code, run the `Akkatecture.Examples.UserAccount.Application` configuration in the IDE. \ No newline at end of file diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs b/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs index 56553610..7c1d29f0 100644 --- a/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs +++ b/examples/walkthrough/Akkatecture.Walkthrough.Application/Program.cs @@ -23,51 +23,63 @@ public static void CreateActorSystem() //Create actor system var system = ActorSystem.Create("bank-system"); + //Create aggregate manager for accounts var aggregateManager = system.ActorOf(Props.Create(() => new AccountManager()),"account-manager"); //Create revenue repository - RevenueRepository = system.ActorOf(Props.Create(() => new RevenueRepository()),"revenue-repository"); + var revenueRepository = system.ActorOf(Props.Create(() => new RevenueRepository()),"revenue-repository"); - //Create subscriber for revenue + //Create subscriber for revenue repository system.ActorOf(Props.Create(() => new RevenueSubscriber(RevenueRepository)),"revenue-subscriber"); - //Create saga manager + //Create saga manager for money transfer system.ActorOf(Props.Create(() => new MoneyTransferSagaManager(() => new MoneyTransferSaga(aggregateManager))),"moneytransfer-saga"); AccountManager = aggregateManager; + RevenueRepository = revenueRepository; } public static void Main(string[] args) { + //initialize actor system CreateActorSystem(); + //create send receiver identifiers var senderId = AccountId.New; var receiverId = AccountId.New; + //create mock opening balances var senderOpeningBalance = new Money(509.23m); var receiverOpeningBalance = new Money(30.45m); + //create commands for opening the sender and receiver accounts var openSenderAccountCommand = new OpenNewAccountCommand(senderId, senderOpeningBalance); var openReceiverAccountCommand = new OpenNewAccountCommand(receiverId, receiverOpeningBalance); + //send the command to be handled by the account aggregate AccountManager.Tell(openReceiverAccountCommand); AccountManager.Tell(openSenderAccountCommand); + //create command to initiate money transfer var amountToSend = new Money(125.23m); var transaction = new Transaction(senderId, receiverId, amountToSend); var transferMoneyCommand = new TransferMoneyCommand(senderId,transaction); + //send the command to initiate the money transfer AccountManager.Tell(transferMoneyCommand); + //fake 'wait' to let the saga process the chain of events Task.Delay(TimeSpan.FromSeconds(1)).Wait(); Console.WriteLine("Walkthrough operations complete.\n\n"); Console.WriteLine("Press Enter to get the revenue:"); Console.ReadLine(); + //get the revenue stored in the repository var revenue = RevenueRepository.Ask(new GetRevenueQuery(), TimeSpan.FromMilliseconds(500)).Result; + //print the results Console.WriteLine($"The Revenue is: {revenue.Revenue.Value}."); Console.WriteLine($"From: {revenue.Transactions} transaction(s)."); diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Sagas/MoneyTransfer/MoneyTransferSagaLocator.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Sagas/MoneyTransfer/MoneyTransferSagaLocator.cs index 8a8a8ca2..5462caf6 100644 --- a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Sagas/MoneyTransfer/MoneyTransferSagaLocator.cs +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Sagas/MoneyTransfer/MoneyTransferSagaLocator.cs @@ -15,10 +15,12 @@ public MoneyTransferSagaId LocateSaga(IDomainEvent domainEvent) { case MoneySentEvent evt: return new MoneyTransferSagaId($"{LocatorIdPrefix}-{evt.Transaction.Id}"); + case MoneyReceivedEvent evt: return new MoneyTransferSagaId($"{LocatorIdPrefix}-{evt.Transaction.Id}"); + default: - throw new ArgumentNullException(nameof(domainEvent)); + throw new ArgumentException(nameof(domainEvent)); } } } diff --git a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs index 3bebdba0..c6837a46 100644 --- a/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs +++ b/examples/walkthrough/Akkatecture.Walkthrough.Domain/Subscribers/RevenueSubscriber.cs @@ -4,7 +4,6 @@ using Akkatecture.Subscribers; using Akkatecture.Walkthrough.Domain.Model.Account; using Akkatecture.Walkthrough.Domain.Model.Account.Events; -using Akkatecture.Walkthrough.Domain.Model.Account.ValueObjects; using Akkatecture.Walkthrough.Domain.Repositories.Revenue.Commands; namespace Akkatecture.Walkthrough.Domain.Subscribers diff --git a/examples/walkthrough/README.md b/examples/walkthrough/README.md index f24bb1d5..ceeb6fc6 100644 --- a/examples/walkthrough/README.md +++ b/examples/walkthrough/README.md @@ -2,16 +2,16 @@ This is the sample that shows the completed result of the [walkthrough](https://akkatecture.net/docs/walkthrough-introduction). -# Akkatecture.Walkthrough.Domain +## Akkatecture.Walkthrough.Domain This is the domain of the sample which follows from the walkthrough tutorial. -### Commands +#### Commands * **OpenNewAccountCommand** - Command for creating a new bank account. * **ReceiveMoneyCommand** - Command for adding funds to a bank account. * **TransferMoneyCommand** - Command for sending funds from a bank account. -### Events +#### Events * **AccountOpenedEvent** - Event that denotes an account has been opened. * **FeesDeductedEvent** - Event that denotes that bank fees have been deducted from an account. * **MoneyReceivedEvent** - Event that denotes that a mank account has received funds. @@ -19,7 +19,17 @@ This is the domain of the sample which follows from the walkthrough tutorial. * **MoneyTransferCompletedEvent** - Event that denotes that a transfer has ended. * **MoneyTransferStartedEvent** - Event that denotes that a money transfer has started. -# Akkatecture.Walkthrough.Application +## Akkatecture.Walkthrough.Application +This application is incharge of instantiating and running the walkthrough example. + +### Description + +The application instantiates all the necessary actors and domain entities required. It then creates two bank accounts using the +`OpenNewAccountCommand`s for the sender and receiver. Finally, the sender makes an intent to send money to the receiver through a +`TransferMoneyCommand`. On sucessful execution of this command, the sender account emits a `MoneySentEvent` that will start a `MoneyTransferSaga`. +The `MoneyTransferSaga` coordinates and fascilitates with the money transfer and tells the receicer account to accept the money. Meanwhile, +The `RevenueSubscriber`, which is subscribed to the `FeesDeductedEvent`, will listen to all of these events and aggregate a "revenue" that the bank has earned. +This result is aggregated into a mock repository called the `RevenueRepository`. We then query the `RevenueRepository` using a `GetRevenueQuery`. > to run the application in jetbrains rider or visual studio code, run the `Akkatecture.Walkthrough.Application` configuration in the IDE. \ No newline at end of file From 50d432deb400a0ffd1b6e7e59305af283cd27dea Mon Sep 17 00:00:00 2001 From: Lutando Ngqakaza Date: Mon, 14 May 2018 19:31:34 +0200 Subject: [PATCH 6/7] changes to readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b91bd79..a487bf29 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ aggregateManager.Tell(changeNameCommand); ### Assumptions About Akkatecture Developers -You should have some expirience in domain driven design, cqrs, and event sourcing. -It would also be benefitial for you to be familiar with actor systems, akka.net, and the extensibility points that akka gives you through hocon configuration. However if you follow the walkthrough on the documentation website you will be acquinted with many akka.net concepts. +It would be ideal if you have some expirience in domain driven design, cqrs, and event sourcing. +It would also be beneficial for you to be familiar with actor systems, akka.net, and the extensibility points that akka gives you through hocon configuration. However, if you follow the [walkthrough](https://akkatecture.net/docs/walkthrough-introduction) you will be acquinted with many akka.net concepts. ### Status of Akkatecture @@ -110,9 +110,9 @@ There are many different opinions and best practices when it comes to building o Doing domain driven design in a distributed scenario is quite tricky. And even more so when you add cqrs and event sourcing style mechanics to your business domain. Akka gives you powerful ways to co-ordinate and organise your business rules by using actors and message passing, which can be done by sending messages through location transparent addresses (or references). The major benefits of using akka.net is that we can isolate our domain models into actors where it makes sense. -Akkatecture gives you a set of semi-opinionated generic constructs that you can use to wire up your application so that you can focus on your main task, modelling and codifying your business domain. +Akkatecture gives you a set of opinionated generic constructs that you can use to wire up your application so that you can focus on your main task, modelling and codifying your business domain. -Akka.net gives us a wealth of good APIs out of the box that can be used to build entire systems out of. It also has a decent ecosystem and [community](https://gitter.im/akkadotnet/akka.net) for support. Akkatecture is also of the opinion that commands translate well semantically in actor systems since commands are telling the actor instance what your intent is, and then it is up to the actor to process the intent as it sees fit, this notion fits well into CQRS because that is basically what a command handler is for, it is to process command intent. +Akka.net gives us a wealth of good APIs out of the box that can be used to build entire systems out of. It also has a decent ecosystem and [community](https://gitter.im/akkadotnet/akka.net) for support. Akkatecture is also of the opinion that commands translate well semantically in actor systems since telling commands is a form of message passing that fits well into the actor model paradigm. ## Acknowledgements From fd2f03b075fa957ef1a4b37fa701ac756508d0d5 Mon Sep 17 00:00:00 2001 From: Lutando Ngqakaza Date: Mon, 14 May 2018 19:32:16 +0200 Subject: [PATCH 7/7] build --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 71ab9957..4957f50a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.0.{build} +version: 0.1.{build} branches: only: - master