public async Task <Unit> Handle(MakeTransfer command, CancellationToken cancellationToken = default) { var accountFrom = await store.AggregateStreamAsync <Account>(command.FromAccountId, token : cancellationToken); accountFrom.RecordOutflow(command.ToAccountId, command.Ammount); var accountFromEvents = accountFrom.DequeueUncommittedEvents(); store.Append(accountFrom.Id, accountFromEvents); var accountTo = await store.AggregateStreamAsync <Account>(command.ToAccountId, token : cancellationToken); accountTo.RecordInflow(command.FromAccountId, command.Ammount); var accountToEvents = accountFrom.DequeueUncommittedEvents(); store.Append(accountTo.Id, accountTo); await session.SaveChangesAsync(cancellationToken); await eventBus.Publish(accountFromEvents); await eventBus.Publish(accountToEvents); return(Unit.Value); }
public async Task <Unit> Handle(MakeTransfer command, CancellationToken cancellationToken = default) { var(fromAccountId, toAccountId, amount) = command; var accountFrom = await Store.AggregateStreamAsync <Account>(fromAccountId, token : cancellationToken) ?? throw AggregateNotFoundException.For <Account>(fromAccountId); accountFrom.RecordOutflow(toAccountId, amount); var accountFromEvents = accountFrom.DequeueUncommittedEvents(); Store.Append(accountFrom.Id, accountFromEvents); var accountTo = await Store.AggregateStreamAsync <Account>(toAccountId, token : cancellationToken) ?? throw AggregateNotFoundException.For <Account>(toAccountId); accountTo.RecordInflow(fromAccountId, amount); var accountToEvents = accountFrom.DequeueUncommittedEvents(); Store.Append(accountTo.Id, accountTo); await session.SaveChangesAsync(cancellationToken); await eventBus.Publish(accountFromEvents); await eventBus.Publish(accountToEvents); return(Unit.Value); }
public void MakeTransfer([FromBody] MakeTransfer transfer) { if (validator.IsValid(transfer)) { Book(transfer); } }
public IActionResult MakeTransfer([FromBody] MakeTransfer cmd) => validate(cmd) .Bind(t => getAccount(t.DebitedAccountId).Debit(t)) .Do(result => saveAndPublish(result.Item1)) .Match <IActionResult>( Invalid: errs => BadRequest(new { Errors = errs }), Valid: result => Ok(new { Balance = result.Item2.Balance }));
Validation <MakeTransfer> ValidateBic(MakeTransfer cmd) { if (!regex.IsMatch(cmd.Bic.ToUpper())) { return(Errors.InvalidBic); } return(cmd); }
// handle commands public static (Event Event, AccountState NewState) Debit (this AccountState @this, MakeTransfer transfer) { var evt = transfer.ToEvent(); var newState = @this.Apply(evt); return(evt, newState); }
//[HttpPost, Route("api/Chapters7/transfers/future")] public IActionResult MakeTransfer([FromBody] MakeTransfer cmd) => validator.Validate(cmd) .Map(repository.Save) .Match( Invalid: BadRequest, Valid: result => result.Match <IActionResult>( Exception: _ => StatusCode(500, Errors.UnexpectedError), Success: _ => Ok()));
void Book(MakeTransfer transfer) => accounts.Get(transfer.DebitedAccountId) .Bind(account => account.Debit(transfer.Amount)) .ForEach(newState => { accounts.Save(transfer.DebitedAccountId, newState); swift.Wire(transfer, newState); });
Validation <MakeTransfer> ValidateDate(MakeTransfer cmd) { if (cmd.Date.Date <= now.Date) { return(Errors.TransferDateIsPast); } return(cmd); }
public void WhenTransferDateIsPast_ThenValidatorFails() { var sut = new DateNotPastValidator_Testable(new FakeDateTimeService()); var transfer = new MakeTransfer { Date = presentDate.AddDays(-1) }; Assert.AreEqual(false, sut.IsValid(transfer)); }
public bool WhenTransferDateIsPast_ThenValidationFails(int offset) { var sut = new DateNotPastValidator_Testable(new FakeDateTimeService()); var transferDate = presentDate.AddDays(offset); var transfer = new MakeTransfer { Date = transferDate }; return(sut.IsValid(transfer)); }
public void WhenTransferDateIsFuture_ThenValidationPasses() { var transfer = new MakeTransfer { Date = new DateTime(2016, 12, 12) }; var validator = new DateNotPastValidator(); var actual = validator.IsValid(transfer); Assert.AreEqual(true, actual); }
Exceptional <Unit> Save(MakeTransfer transfer) { try { ConnectionHelper.Connect(connString , c => c.Execute("INSERT ...", transfer)); } catch (Exception ex) { return(ex); } return(Unit()); }
Either <Error, MakeTransfer> ValidateDate(MakeTransfer request) { if (request.Date.Date <= now.Date) { return(Errors.TransferDateIsPast); } else { return(request); } }
public bool WhenTransferDateIsPAst_ThenValidationFails(int offset) { // passing that fake makes call to validator pure // and makes test consistent var sut = new DateNotPastValidatorPure(new FakeDateTimeService()); var cmd = new MakeTransfer { Date = presentDate.AddDays(offset) }; return(sut.IsValid(cmd)); }
Either <Error, MakeTransfer> ValidateBic(MakeTransfer request) { if (!bicRegex.IsMatch(request.Bic)) { return(Errors.InvalidBic); } else { return(request); } }
public static DebitedTransfer ToEvent(this MakeTransfer cmd) => new DebitedTransfer { Beneficiary = cmd.Beneficiary, Bic = cmd.Bic, DebitedAmount = cmd.Amount, EntityId = cmd.DebitedAccountId, Iban = cmd.Iban, Reference = cmd.Reference, Timestamp = cmd.Timestamp };
public IActionResult MakeTransfer([FromBody] MakeTransfer cmd) { var account = getAccount(cmd.DebitedAccountId); // performs the transfer var(evt, newState) = account.Debit(cmd); saveAndPublish(evt); // returns information to the user about the new state return(Ok(new { Balance = newState.Balance })); }
public Task <IActionResult> MakeTransfer([FromBody] MakeTransfer command) { Task <Validation <AccountState> > outcome = from cmd in Async(Validate(command)) from acc in GetAccount(cmd.DebitedAccountId) from result in acc.Handle(cmd) select result.NewState; return(outcome.Map( Faulted: ex => StatusCode(500, Errors.UnexpectedError), Completed: val => val.Match( Invalid: errs => BadRequest(new { Errors = errs }), Valid: newState => Ok(new { Balance = newState.Balance }) as IActionResult))); }
public void WhenTransferDateIsPast_ThenValidatorFails(int offset, bool expected) { // Given var sut = new DateNotPastValidator(_presentDate); var cmd = new MakeTransfer { Date = _presentDate.AddDays(offset) }; // When var result = sut.IsValid(cmd); // Then Assert.AreEqual(expected, result); }
private static void MakeTransfer(DocumentStore documentStore, MakeTransfer moneyTransfer) { using (var session = documentStore.OpenSession()) { var accountFrom = session.Events.AggregateStream <Account>(moneyTransfer.FromAccountId); accountFrom.RecordOutflow(moneyTransfer.ToAccountId, moneyTransfer.Ammount); session.Events.Append(accountFrom.Id, accountFrom.PendingEvents.ToArray()); var accountTo = session.Events.AggregateStream <Account>(moneyTransfer.ToAccountId); accountTo.RecordInflow(moneyTransfer.FromAccountId, moneyTransfer.Ammount); session.Events.Append(accountTo.Id, accountTo.PendingEvents.ToArray()); session.SaveChanges(); } }
public void bic_exists_valid_should_be_expected(string bic, bool expected) { // Arrange string[] validCodes = { "A" }; var cmd = new MakeTransfer { Bic = bic }; var sut = new BicExistsValidator(() => validCodes); // Act var actual = sut.IsValid(cmd); // Assert Assert.Equal(expected, actual); }
// handle commands public static Validation <(Event Event, AccountState NewState)> Debit (this AccountState @this, MakeTransfer cmd) { if (@this.Status != AccountStatus.Active) { return(Errors.AccountNotActive); } if (@this.Balance - cmd.Amount < @this.AllowedOverdraft) { return(Errors.InsufficientBalance); } var evt = cmd.ToEvent(); var newState = @this.Apply(evt); return(evt as Event, newState); }
public void valid_when_date_is_in_the_past_should_be_false() { // Arrange //var fake = new Mock<IDateTimeService>(); //fake.SetupGet(x => x.UtcNow).Returns(presentDate); //var sut = new DateNotPastValidator(fake.Object); var sut = new DateNotPastValidator(presentDate); var cmd = new MakeTransfer { Date = presentDate.AddDays(-1) }; // Act var actual = sut.IsValid(cmd); // Assert Assert.False(actual); }
public Task <IActionResult> Transfer([FromBody] MakeTransfer command) { Task <Validation <AccountState> > outcome = from cmd in Async(validate(command)) from acc in GetAccount(cmd.DebitedAccountId) from result in Async(Account.Debit(acc, cmd)) from _ in SaveAndPublish(result.Item1).Map(Valid) select result.Item2; //Task<Validation<AccountState>> a = validate(command) // .Traverse(cmd => getAccount(cmd.DebitedAccountId) // .Bind(acc => Account.Debit(acc, cmd) // .Traverse(result => saveAndPublish(result.Item1) // .Map(_ => result.Item2)))) // .Map(vva => vva.Bind(va => va)); // flatten the nested validation inside the task return(outcome.Map( Faulted: ex => StatusCode(500, Errors.UnexpectedError), Completed: val => val.Match( Invalid: errs => BadRequest(new { Errors = errs }), Valid: newState => Ok(new { Balance = newState.Balance }) as IActionResult))); }
public void AccountIsOnlyLoadedOnce() { int accountLoaded = 0; int changesPersisted = 0; var accountState = new AccountState ( Currency: "EUR", Balance: 1000, Status: AccountStatus.Active ); var registry = new AccountRegistry( loadAccount: _ => { accountLoaded++; return(Async(Some(accountState))); }, saveAndPublish: _ => { changesPersisted++; return(Async(Unit())); }); var controller = new MakeTransferController( validate: Valid, getAccount: id => registry.Lookup(id)); // make 2 transfers var cmd = new MakeTransfer { Amount = 200 }; var x = controller.MakeTransfer(cmd).Result; var y = controller.MakeTransfer(cmd).Result; Assert.AreEqual(2, changesPersisted); Assert.AreEqual(1, accountLoaded); }
Validation <MakeTransfer> ValidateDate(MakeTransfer transfer) => transfer.Date.Date > now.Date ? transfer : Errors.TransferDateIsPast;
Validation <MakeTransfer> ValidateBic(MakeTransfer transfer) => regex.IsMatch(transfer.Bic.ToUpper()) ? transfer : Errors.InvalidBic;
Validation <MakeTransfer> Validate(MakeTransfer cmd) => ValidateBic(cmd).Bind(ValidateDate);
Validation <Exceptional <Unit> > Handle(MakeTransfer request) => Validate(request) .Map(Save);