Last active
March 9, 2024 08:53
-
-
Save akhansari/c2d57470d10aacd04aae71258c52bfc1 to your computer and use it in GitHub Desktop.
C# prototype of the 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.Account; | |
using Events = IReadOnlyCollection<IEvent>; | |
public record Transaction(decimal Amount, DateTime Date); | |
// events | |
public interface IEvent { } // used to mimic a discriminated union | |
public record Deposited(Transaction Transaction) : IEvent; | |
public record Withdrawn(Transaction Transaction) : IEvent; | |
public record Closed(DateTime Date) : IEvent; | |
// commands | |
public interface ICommand { } | |
public record Deposit(decimal Amount, DateTime Date) : ICommand; | |
public record Withdraw(decimal Amount, DateTime Date) : ICommand; | |
public record Close(DateTime Date) : ICommand; | |
public record State(decimal Balance, bool IsClosed) | |
{ | |
public static readonly State Initial = new(0, false); | |
} | |
public static class Decider | |
{ | |
// handle state | |
private static State Evolve(State state, IEvent @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 State Fold(this IEnumerable<IEvent> history, State state) => | |
history.Aggregate(state, Evolve); | |
public static State Fold(this IEnumerable<IEvent> history) => | |
history.Fold(State.Initial); | |
public static bool IsTerminal(this State state) => state.IsClosed; | |
// handle commands | |
public static Events Decide(this State state, ICommand command) => | |
command switch | |
{ | |
Deposit c => Deposit(c), | |
Withdraw c => Withdraw(c), | |
Close c => Close(state, c), | |
_ => throw new NotImplementedException() | |
}; | |
private static Events Deposit(Deposit c) => | |
new Deposited(new(c.Amount, c.Date)).Singleton(); | |
private static Events Withdraw(Withdraw c) => | |
new Withdrawn(new(c.Amount, c.Date)).Singleton(); | |
private static Events Close(State state, Close c) | |
{ | |
var events = new List<IEvent>(); | |
if (state.Balance > 0) | |
events.Add(new Withdrawn(new(state.Balance, c.Date))); | |
events.Add(new Closed(c.Date)); | |
return events; | |
} | |
// helpers | |
public static Events Singleton(this IEvent e) => new IEvent[1] { e }; | |
} |
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; | |
using EsBankAccount.Account; | |
public class AccountShould | |
{ | |
[Fact] | |
public void Make_a_deposit() => | |
State.Initial | |
.Decide(new Deposit(5, DateTime.MinValue)) | |
.Should() | |
.Equal(new Deposited(new(5, DateTime.MinValue)).Singleton()); | |
[Fact] | |
public void Make_a_withdrawal() => | |
State.Initial | |
.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 IEvent[] | |
{ | |
new Deposited(new(5, DateTime.MinValue)), | |
new Deposited(new(5, DateTime.MinValue)) | |
} | |
.Fold() | |
.Decide(new Close(DateTime.MinValue)) | |
.Should() | |
.Equal(new IEvent[] | |
{ | |
new Withdrawn(new(10, DateTime.MinValue)), | |
new Closed(DateTime.MinValue) | |
}); | |
} |
Hi Christian, yes Fluent Assertions is used here. I forgot I had activated implicit usings.
Thx Amin got it working. Terrific example!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Amin From where do you get the .Should() method ? Fluent Assertions ?