-
-
Save dmmusil/c103aff32b72dd9ebd77c00a4b984439 to your computer and use it in GitHub Desktop.
C# prototype of Decider pattern. F# version: https://github.com/akhansari/EsBankAccount
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace EsBankAccount | |
| { | |
| // events | |
| public interface IBankAccountEvent { } | |
| public record Transaction(decimal Amount, DateTime Date); | |
| public record Deposited(Transaction Transaction) : IBankAccountEvent; | |
| public record Withdrawn(Transaction Transaction) : IBankAccountEvent; | |
| public record Closed(DateTime Date) : IBankAccountEvent; | |
| // commands | |
| public interface IBankAccountCommand { } | |
| public record Deposit(decimal Amount, DateTime Date) : IBankAccountCommand; | |
| public record Withdraw(decimal Amount, DateTime Date) : IBankAccountCommand; | |
| public record Close(DateTime Date) : IBankAccountCommand; | |
| public static class BankAccount | |
| { | |
| public static readonly IReadOnlyCollection<IBankAccountEvent> NoHistory = new List<IBankAccountEvent>(); | |
| public static IReadOnlyCollection<IBankAccountEvent> Singleton(this IBankAccountEvent e) => | |
| new List<IBankAccountEvent>(1) { e }; | |
| // handle state | |
| public record State(decimal Balance, bool IsClosed); | |
| private static readonly State InitialState = new(0, false); | |
| private static State Evolve(State state, IBankAccountEvent @event) => | |
| @event switch | |
| { | |
| Deposited deposited => state with { Balance = state.Balance + deposited.Transaction.Amount }, | |
| Withdrawn withdrawn => state with { Balance = state.Balance - withdrawn.Transaction.Amount }, | |
| Closed _ => state with { IsClosed = true }, | |
| _ => state | |
| }; | |
| public static bool IsTerminal(State state) => state.IsClosed; | |
| public static State Fold(this IEnumerable<IBankAccountEvent> history, State state) => | |
| history.Aggregate(state, Evolve); | |
| public static State Fold(this IEnumerable<IBankAccountEvent> history) => | |
| history.Fold(InitialState); | |
| // handle commands | |
| private static IReadOnlyCollection<IBankAccountEvent> Deposit(Deposit c) => | |
| new Deposited(new(c.Amount, c.Date)).Singleton(); | |
| private static IReadOnlyCollection<IBankAccountEvent> Withdraw(Withdraw c) => | |
| new Withdrawn(new(c.Amount, c.Date)).Singleton(); | |
| private static IReadOnlyCollection<IBankAccountEvent> Close(State state, Close c) | |
| { | |
| var events = new List<IBankAccountEvent>(); | |
| if (state.Balance > 0) | |
| events.Add(new Withdrawn(new(state.Balance, c.Date))); | |
| events.Add(new Closed(c.Date)); | |
| return events; | |
| } | |
| public static IReadOnlyCollection<IBankAccountEvent> Decide(this State state, IBankAccountCommand command) => | |
| command switch | |
| { | |
| Deposit c => Deposit(c), | |
| Withdraw c => Withdraw(c), | |
| Close c => Close(state, c), | |
| _ => throw new NotImplementedException() | |
| }; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace EsBankAccount.Tests | |
| { | |
| public class BankAccount_Should | |
| { | |
| [Fact] | |
| public void Make_a_deposit() => BankAccount | |
| .NoHistory.Fold() | |
| .Decide(new Deposit(5, DateTime.MinValue)) | |
| .Should() | |
| .Equal(new Deposited(new(5, DateTime.MinValue)).Singleton()); | |
| [Fact] | |
| public void Make_a_withdrawal() => BankAccount | |
| .NoHistory.Fold() | |
| .Decide(new Withdraw(5, DateTime.MinValue)) | |
| .Should() | |
| .Equal(new Withdrawn(new(5, DateTime.MinValue)).Singleton()); | |
| [Fact] | |
| public void Close_the_account_and_withdraw_the_remaining_amount() => | |
| new List<IBankAccountEvent>() | |
| { | |
| new Deposited(new(5, DateTime.MinValue)), | |
| new Deposited(new(5, DateTime.MinValue)) | |
| } | |
| .Fold() | |
| .Decide(new Close(DateTime.MinValue)) | |
| .Should() | |
| .Equal(new List<IBankAccountEvent> | |
| { | |
| new Withdrawn(new(10, DateTime.MinValue)), | |
| new Closed(DateTime.MinValue) | |
| }); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment