Time to break the silence!
A lot of things have happened since I last wrote.
I’ve got a new job at nethouse.se as developer and mentor.
Akka.NET have been doing some crazy progress the last few months.
When I last wrote, we were only two developers, now, we are about 10 core developers.
The project also have a new fresh site here: akkadotnet.github.com.
We are at version 0.7 right now, but pushing hard for a 1.0 release as soon as possible.
But not, let’s get back on topic.
In this mini tutorial, I will show how to deal with concurrency using Akka.NET.
Let’s say we need to model a bank account.
That is a classic concurrency problem.
If we would use OOP, we might start with something like this:
public class BankAccount { private decimal _balance; public void Withdraw(decimal amount) { if (_balance < amount) throw new ArgumentException( "amount may not be larger than account balance"); _balance -= amount; //... more code } //... more code }
That seems fair, right?
This will work fine in a single threaded environment where only one thread is accessing the above code.
But what happens when two or more competing threads are calling the same code?
public void Withdraw(decimal amount) { if (_balance < amount) //<-
That if-statement might be running in parallel on two or more theads, and at that very point in time, the balance is still unchanged. so all threads gets past the guard that is supposed to prevent a negative balance.
So in order to deal with this we need to introduce locks.
Maybe something like this:
private readonly object _lock = new object(); private decimal _balance; public void Withdraw(decimal amount) { lock(_lock) //<- { if (_balance < amount) throw new ArgumentException( "amount may not be larger than account balance"); _balance -= amount; } }
This prevents multiple threads from accessing and modifying the state inside the Withdraw method at the same time.
So all is fine and dandy, right?
Not so much.. locks are bad for scaling, threads will end up waiting for resources to be freed up.
And in bad cases, your software might spend more time waiting for resources than it does actually running business code.
It will also make your code harder to read and reason about, do you really want threading primitives in your business code?
Here is where the Actor Model and Akka.NET comes into the picture.
The Actor Model makes actors behave “as if” they are single threaded.
Actors have a concurrency constraint that prevents it from processing more than one message at any given time.
This still applies if there are multiple producers passing messages to the actor.
So let’s model the same problem using an Akka.NET actor:
//immutable message for withdraw: public class Withdraw { public readonly decimal Amount; public Withdraw(decimal amount) { Amount = amount; } } //the account actor public class BankAccount : TypedActor, IHandle<Withdraw> { private decimal _balance; public void Handle(Withdraw withdraw) { if (_balance < amount) { Sender.Tell("fail")); //you should use real message types here return; } _balance -= withdraw.Amount; Sender.Tell("success); //and here too } }
So what do we have here?
We have a message class that represents the Withdraw message, the actor model relies on async message passing.
The BankAccount actor, is then told to handle any message of the type Withdraw by subtracting the amount from the balance.
If the amount is too large, the actor will reply to it’s sender telling it that the operation failed due to a too large amount trying to be withdrawn.
In the example code, I use strings as the response on the status of the operation, you probably want to use some real message types for this purpose. but to keep the example small, strings will do fine.
How do we use this code then?
ActorSystem system = ActorSystem.Create("mysystem"); ... var account = system.ActorOf<BankAccount>(); var result = await account.Ask(new Withdraw(100)); //result is now "success" or "fail"
Thats about it, we now have a completely lock free implementation of an bank account.
Feel free to ask any question :-)
Could you expand on your eample? Do you actually have an instance of every single account as an Actor? If so then how would handle a transfer between accounts? Would have a supervisor per account holder? How would you deal with persistence?
Cheers,
Jason Wyglendowski
@Jason
Yes, you could have an actor per bank account, actors are lightweight, ca 400 bytes each by default.
You could also use e.g. a consistent hash router to route work for a given account ID, to a specific worker. http://akkadotnet.github.io/wiki/Routing#consistenthash
So basically if I wanted to transfer between two accounts there would have to be a supervisor actor that represents the account holder then that actor could spin up two account actors and coordinate the work between them. Is that a fair understanding? Could I think of it like spinning up threads or tasks to do work except with actor constraints? The concept seems very similar to Message Handlers . I guess I will spend some time doing a bit more research plus check out the provide link. One of the things I am trying to conceptualize besides the proper way to handle transactions between actors is using them to model Aggregates in DDD (i.e. https://github.com/jcwrequests/lokad-iddd-sample-clone/blob/master/Sample/Domain/CustomerAggregate/CustomerApplicationService.cs).
Cheers,
Jason Wyglendowski
Sorry I should have included this in the previous comment but would I just follow the steps documented here http://c2.com/cgi/wiki?TransactionalActorModel to do transactions?
Thanks Again,
Cheers,
Jason Wyglendowski
@Jason, yes, it is very similar to message handlers, actually, we do support a variant of actors that we call “TypedActor” that looks just like that, it has a `IHandle` interface that you apply to the typed actor.
So when modelling an DDD/CQRS Aggregate root, an actor is a perfect fit, it is a 1 to 1 conceptual mapping pretty much.
Aggregate roots are consistency boundaries, Actors have a concurrency constraint which makes them consistent with themselves.
As for transactions, that is a tricky matter, the consensus in the distributed community is that distributed transactions are slow and should be avoided.
So the pattern that is emerging for this is rather to model transactions much like they work in the real world.
e.g. If I buy something on credit from a store, if the store don’t get their payment in x time, they will remind me, and if they still dont get their money, they will fine me.
You can model your actors the very same way.
Make each actor know about its own responsibillities and what other parties it has interactions with.
You should model your transactions as a real world business flow, this way, you don’t need some magical algorithm/code implementation that somehow knows how to handle your specific business case in a magical generic way, instead, you make up a process of messages that should occur when someone does not fullfill their part of the transaction contract and then act upon that.
Does that make sense?
Yes that makes sense. So if I was to transfer money from one account to another I believe this could be accomplished by modeling properly the relationship be between the two accounts. I could have an Aggregate that represents the customer. The customers state would include probably demographic information and a list of accounts. The accounts would be Value Objects and do something similar to the examples http://blog.canberger.se/2014/10/getting-started-with-akkanet.html. Instead of add like in this case it would be transfer. When the events are stored to the event store as a single write I have essentialy my transaction. If I needed to transfer money from one customer to another like in a bill payment scenario I definitely see the benefit of using sagas. In that case you could have a hold amount command that waits for the final Ack command from the other Aggregate. You would be forced to use things like transaction scope when using tranactional resources like MSMQ but that can be done simply enough. What I would like to do is actually but in code what we have discussed. First I would probably do it as normal DDD ciode then attempt the samething using Akka. Thanks again I did see your other posf and I appreciate the discussion. I apologize for any spelling mistakes it’s difficult to type using my tablet. Cheers, Jason Wyglendowski
Shouldn’t it be (_balance < withdraw.Amount )?