4.4 KiB
languages | products | page_type | name | urlFragment | description | |||
---|---|---|---|---|---|---|---|---|
|
|
sample | Orleans BankAccount ACID transactions | orleans-bank-account-acid-transactions | An example of a BankAccount using ACID transactions. |
Orleans Bank Account with ACID transactions
This sample demonstrates how to implement ACID transactions using Orleans using a bank account scenario. There are two kinds of grains:
AccountGrain
, which implementsIAccountGrain
, simulates a bank account with a balance.AtmGrain
, which implementsIAtmGrain
, simulates an Automatic Teller Machine that allows transfers between two bank accounts.
AtmGrain
has this interface:
public interface IAtmGrain : IGrainWithIntegerKey
{
[Transaction(TransactionOption.Create)]
Task Transfer(Guid fromAccount, Guid toAccount, uint amountToTransfer);
}
AccountGrain
has this interface:
public interface IAccountGrain : IGrainWithGuidKey
{
[Transaction(TransactionOption.Join)]
Task Withdraw(uint amount);
[Transaction(TransactionOption.Join)]
Task Deposit(uint amount);
[Transaction(TransactionOption.CreateOrJoin)]
Task<uint> GetBalance();
}
The [Transaction(option)]
attributes on the grain methods tell the runtime that these methods are transactional.
The IAtmGrain.Transfer
method creates a transaction, while the IAccountGrain.Withdraw
and IAccountGrain.Deposit
methods must be called in the context of existing transactions.
AtmGrain.Transfer(...)
is implemented as follows:
public async Task Transfer(
IAccountGrain fromAccount,
IAccountGrain toAccount,
uint amountToTransfer)
{
await Task.WhenAll(
fromAccount.Withdraw(amountToTransfer),
toAccount.Deposit(amountToTransfer));
}
The Transfer
method withdraws the specified amount from one IAccountGrain
and deposits it in the other. Orleans ensures that this occurs in the context of a transaction to ensure consistency.
The AccountGrain.Deposit
method adds the deposited amount to the account balance using the ITransactionalState<T>.PerformUpdate
method:
public Task Deposit(uint amount) => _balance.PerformUpdate(x => x.Value += amount);
Real banks allow overdrawing accounts, but this sample does not. AccountGrain.Withdraw(uint amount)
prevents overdrawing by throwing an exception, causing the transaction to be aborted:
public Task Withdraw(uint amount) => _balance.PerformUpdate(x =>
{
if (x.Value < amount)
{
throw new InvalidOperationException(
$"Withdrawing {amount} credits from account \"{this.GetPrimaryKeyString()}\" would overdraw it."
+ $" This account has {x.Value} credits.");
}
x.Value -= amount;
});
Sample prerequisites
This sample is written in C# and targets .NET 7.0. It requires the .NET 7.0 SDK or later.
Building the sample
To download and run the sample, follow these steps:
- Download and unzip the sample.
- In Visual Studio (2022 or later):
- On the menu bar, choose File > Open > Project/Solution.
- Navigate to the folder that holds the unzipped sample code, and open the C# project (.csproj) file.
- Choose the F5 key to run with debugging, or Ctrl+F5 keys to run the project without debugging.
- From the command line:
- Navigate to the folder that holds the unzipped sample code.
- At the command line, type
dotnet run
.
First start the BankServer process
dotnet run --project BankServer
Then start the BankClient process
dotnet run --project BankClient
The client will issue transactions between random accounts in a loop, printing the results. For example:
We transferred 100 credits from Pasqualino to Ida.
Pasqualino balance: 1500
Ida balance: 1600
When a withdraw would overdraw an account, the client will print an error like so:
Error transferring 100 credits from Derick to Xaawo: Transaction 2edc92f5-a94d-4167-9522-fa661cc030ff Aborted because of an unhandled exception in a grain method call. See InnerException for details.
InnerException: Withdrawing 100 credits from account "Derick" would overdraw it. This account has 0 credits.