public void Execute(Guid fromAccountId, Guid toAccountId, decimal amount)
        {
            var from = _accountRepository.GetAccountById(fromAccountId);
            var to   = _accountRepository.GetAccountById(toAccountId);

            //These kinds of validations could be wrapped using FluentValidation, but for simplicity because its only one validation I decided to do it in here.
            //That being said, although it's only one validation we can already see code duplication in the other feature, which could go against clean code practices,
            //so in the end I would actually implement a common validation setup with fluent validation to avoid that, or another strategy to avoid code validation duplications.
            if (from == null || to == null)
            {
                throw new InvalidOperationException($" The user with account id {fromAccountId} does not exist.");
            }

            //If at any point any of these operations fails an exception is thrown and the changes to the entities are safely discarded.
            //But usually this would be wrapped within a unit of work.
            from.TryWithdrawn(amount);
            to.TryTransfer(amount);

            _accountRepository.Update(from);
            _accountRepository.Update(to);

            //Notifications are only sent after we can confirm that the transactions happened successfully.
            //As explain in the account class, the preferred option, in my opinion, is to have a domain event dispatch this action using a mediator from inside the domain object.
            //Whatever strategy used the key point is to avoid adding any dependencies inside the domain object unless otherwise advised by the team.
            NotificationThresholds.SendIfLowFunds(_notificationService, from);
            NotificationThresholds.SendIfLowFunds(_notificationService, to);
            NotificationThresholds.SendIfNearPaidInLimit(_notificationService, to);
        }
示例#2
0
        public void FullTest_WhenStockIsWithinThresholds_ShouldNotCreateMessage()
        {
            // Prepare return values
            var stock = new StockInfo {
                Symbol = "MSFT", Date = DateTime.Now, Value = 10.0M
            };
            var thresholds = new NotificationThresholds {
                Symbol = "MSFT", Email = "*****@*****.**", High = 15, Low = 4
            };

            // Stub the StockApi
            var stockApi = new Mock <IStockApi>();

            stockApi
            .Setup(s => s.GetStock(It.IsAny <string>()))
            .Returns(Task.FromResult(stock))
            .Verifiable();

            // Stub the Database
            var database = new Mock <IDatabase>();

            database
            .Setup(d => d.GetThresholds(It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.FromResult(thresholds));

            // Stub the MessageService
            var messageSvc = new Mock <IMessageService>();

            messageSvc
            .Setup(m => m.SendMessage(It.IsAny <Message>()));

            // Build up feature class
            var notifier = new StockThresholdNotifier(database.Object, stockApi.Object, messageSvc.Object);

            // Run
            notifier.CheckStock("MSFT", "*****@*****.**")
            .Wait();

            // Assert expected argument value
            stockApi.Verify(s => s.GetStock("MSFT"));

            // Assert expected argument values
            database.Verify(d => d.GetThresholds("MSFT", "*****@*****.**"));
        }
示例#3
0
 /// <summary>
 /// This pure function creates a notification (or not) based on the stock info and thresholds.
 /// </summary>
 public Message MaybeCreateMessage(StockInfo stock, NotificationThresholds thresholds)
 {
     if (stock.Value > thresholds.High)
     {
         return new Message {
                    Email = thresholds.Email, Body = $"{stock.Symbol} exceeds the maximum value of ${thresholds.High}."
         }
     }
     ;
     else if (stock.Value < thresholds.Low)
     {
         return new Message {
                    Email = thresholds.Email, Body = $"{stock.Symbol} is less than the minimum value of ${thresholds.Low}."
         }
     }
     ;
     else
     {
         return(null);
     }
 }