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); }
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", "*****@*****.**")); }
/// <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); } }