예제 #1
0
        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);
        }
예제 #2
0
        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);
        }
예제 #3
0
 public void MakeTransfer([FromBody] MakeTransfer transfer)
 {
     if (validator.IsValid(transfer))
     {
         Book(transfer);
     }
 }
예제 #4
0
 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);
            }
예제 #7
0
 //[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);
 }
예제 #10
0
        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));
        }
예제 #11
0
        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));
        }
예제 #12
0
        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());
 }
예제 #14
0
 Either <Error, MakeTransfer> ValidateDate(MakeTransfer request)
 {
     if (request.Date.Date <= now.Date)
     {
         return(Errors.TransferDateIsPast);
     }
     else
     {
         return(request);
     }
 }
예제 #15
0
        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));
        }
예제 #16
0
 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)));
        }
예제 #20
0
        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);
        }
예제 #21
0
        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();
            }
        }
예제 #22
0
        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);
        }
예제 #23
0
        // 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);
        }
예제 #24
0
        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)));
        }
예제 #26
0
        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);