public static async Task <HttpResponseMessage> SetBeneficialOwnerRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = "SetBeneficialOwner/{accountnumber}/{ownername}")] HttpRequestMessage req,
            string accountnumber,
            string ownername,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (await bankAccountEvents.Exists())
            {
                if (!string.IsNullOrEmpty(ownername))
                {
                    Account.Events.BeneficiarySet evtBeneficiary = new Account.Events.BeneficiarySet()
                    {
                        BeneficiaryName = ownername
                    };
                    await bankAccountEvents.AppendEvent(evtBeneficiary);
                }

                return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.OK,
                                                             FunctionResponse.CreateResponse(startTime,
                                                                                             false,
                                                                                             $"Beneficial owner of account {accountnumber} set"),
                                                             FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.OK,
                                                             FunctionResponse.CreateResponse(startTime,
                                                                                             true,
                                                                                             $"Account {accountnumber} does not exist"),
                                                             FunctionResponse.MEDIA_TYPE));
            }
        }
        public async Task CreateNew_TestMethod()
        {
            bool expected = true;
            bool actual   = false;

            EventStream testObj = new EventStream(new EventStreamAttribute("Bank",
                                                                           "Account", Guid.NewGuid().ToString()));

            MockEventOne testEvent = new MockEventOne()
            {
                EventTypeName = "Another test Event Happened"
            };

            await testObj.AppendEvent(testEvent);

            actual = await testObj.Exists();

            Assert.AreEqual(expected, actual);
        }
        public async Task Append_Event_TestMethod()
        {
            bool expected = true;
            bool actual   = false;

            EventStream testObj = new EventStream(new EventStreamAttribute("Bank",
                                                                           "Account", "Instance 1234"));

            MockEventOne testEvent = new MockEventOne()
            {
                EventTypeName = "Test Event Happened"
            };

            await testObj.AppendEvent(testEvent);

            actual = await testObj.Exists();

            Assert.AreEqual(expected, actual);
        }
        public static async Task <HttpResponseMessage> DepositMoneyRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"DepositMoney/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.NotFound,
                                                             FunctionResponse.CreateResponse(startTime,
                                                                                             true,
                                                                                             $"Account {accountnumber} does not exist"),
                                                             FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                // get the request body...
                MoneyDepositData data = await req.Content.ReadAsAsync <MoneyDepositData>();

                // create a deposited event
                DateTime dateDeposited = DateTime.UtcNow;
                Account.Events.MoneyDeposited evDeposited = new Account.Events.MoneyDeposited()
                {
                    LoggedDepositDate = dateDeposited,
                    AmountDeposited   = data.DepositAmount,
                    Commentary        = data.Commentary,
                    Source            = data.Source
                };

                await bankAccountEvents.AppendEvent(evDeposited);

                return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.OK,
                                                             FunctionResponse.CreateResponse(startTime,
                                                                                             false,
                                                                                             $"{data.DepositAmount} deposited to account {accountnumber} "),
                                                             FunctionResponse.MEDIA_TYPE));
            }
        }
        public static async Task <Tuple <string, bool> > AccrueInterestForSpecificAccount
            ([ActivityTrigger] IDurableActivityContext accrueInterestContext)
        {
            const decimal DEBIT_INTEREST_RATE  = 0.001M;
            const decimal CREDIT_INTEREST_RATE = 0.0005M;

            string accountNumber = accrueInterestContext.GetInput <string>();

            #region Tracing telemetry
            Activity.Current.AddTag("Account Number", accountNumber);
            #endregion

            if (!string.IsNullOrEmpty(accountNumber))
            {
                EventStream bankAccountEvents = new EventStream(new EventStreamAttribute("Bank", "Account", accountNumber));
                if (await bankAccountEvents.Exists())
                {
                    // Has the accrual been done today for this account?
                    Classification         clsAccruedToday = new Classification(new ClassificationAttribute("Bank", "Account", accountNumber, nameof(InterestAccruedToday)));
                    ClassificationResponse isAccrued       = await clsAccruedToday.Classify <InterestAccruedToday>();

                    if (isAccrued.Result != ClassificationResponse.ClassificationResults.Include)
                    {
                        // Get the account balance
                        Projection prjBankAccountBalance = new Projection(new ProjectionAttribute("Bank", "Account", accountNumber, nameof(Balance)));

                        // Get the current account balance, as at midnight
                        Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(DateTime.Today);

                        if (null != projectedBalance)
                        {
                            Account.Events.InterestAccrued evAccrued = new Account.Events.InterestAccrued()
                            {
                                Commentary           = $"Daily scheduled interest accrual",
                                AccrualEffectiveDate = DateTime.Today  // set the accrual to midnight today
                            };
                            // calculate the accrual amount
                            if (projectedBalance.CurrentBalance >= 0)
                            {
                                // Using the credit rate
                                evAccrued.AmountAccrued        = CREDIT_INTEREST_RATE * projectedBalance.CurrentBalance;
                                evAccrued.InterestRateInEffect = CREDIT_INTEREST_RATE;
                            }
                            else
                            {
                                // Use the debit rate
                                evAccrued.AmountAccrued        = DEBIT_INTEREST_RATE * projectedBalance.CurrentBalance;
                                evAccrued.InterestRateInEffect = DEBIT_INTEREST_RATE;
                            }

                            try
                            {
                                await bankAccountEvents.AppendEvent(evAccrued, isAccrued.AsOfSequence);
                            }
                            catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException)
                            {
                                // We can't be sure this hasn't already run...
                                return(new Tuple <string, bool>(accountNumber, false));
                            }
                        }
                    }
                }
            }
            return(new Tuple <string, bool>(accountNumber, true));
        }
コード例 #6
0
        /// <summary>
        /// Perform the underlying validation on the specified command
        /// </summary>
        /// <param name="commandId">
        /// The unique identifier of the command to validate
        /// </param>
        private static async Task <bool> ValidateCreateLeagueCommand(string commandId,
                                                                     ILogger log,
                                                                     IWriteContext writeContext = null)
        {
            const string COMMAND_NAME = @"create-league";

            Guid commandGuid;

            // use custom assembly resolve handler
            using (new AzureFunctionsResolveAssembly())
            {
                if (Guid.TryParse(commandId, out commandGuid))
                {
                    #region Logging
                    if (null != log)
                    {
                        log.LogDebug($"Validating command {commandId} in ValidateCreateLeagueCommand");
                    }
                    #endregion

                    // Get the current state of the command...
                    Projection getCommandState = new Projection(Constants.Domain_Command,
                                                                COMMAND_NAME,
                                                                commandGuid.ToString(),
                                                                nameof(Command_Summary_Projection));

                    if (null != getCommandState)
                    {
                        #region Logging
                        if (null != log)
                        {
                            log.LogDebug($"Projection processor created in ValidateCreateLeagueCommand");
                        }
                        #endregion

                        Command_Summary_Projection cmdProjection =
                            new Command_Summary_Projection(log);

                        await getCommandState.Process(cmdProjection);

                        if ((cmdProjection.CurrentSequenceNumber > 0) || (cmdProjection.ProjectionValuesChanged()))
                        {
                            #region Logging
                            if (null != log)
                            {
                                log.LogDebug($"Command { cmdProjection.CommandName} projection run for {commandGuid} in ValidateCreateLeagueCommand");
                            }
                            #endregion

                            if (cmdProjection.CurrentState ==
                                Command_Summary_Projection.CommandState.Completed)
                            {
                                // No need to validate a completed command
                                #region Logging
                                if (null != log)
                                {
                                    log.LogWarning($"Command {commandId} is complete so no need to validate in ValidateCreateLeagueCommand");
                                }
                                #endregion
                                return(true);
                            }

                            if (cmdProjection.CurrentState ==
                                Command_Summary_Projection.CommandState.Validated)
                            {
                                // No need to process a completed projection
                                #region Logging
                                if (null != log)
                                {
                                    log.LogWarning($"Command {commandId} is validated so no need to validate again in ValidateCreateLeagueCommand");
                                }
                                #endregion
                                return(true);
                            }

                            if ((cmdProjection.CurrentState ==
                                 Command_Summary_Projection.CommandState.Created) ||
                                (cmdProjection.CurrentState ==
                                 Command_Summary_Projection.CommandState.Invalid))
                            {
                                bool leagueNameValid      = false;
                                bool incoporatedDateValid = false;

                                // New or previously invalid command can be validated
                                if (cmdProjection.ParameterIsSet(nameof(Create_New_League_Definition.LeagueName)))
                                {
                                    // League name may not be blank
                                    string leagueName = cmdProjection.GetParameter <string>(nameof(Create_New_League_Definition.LeagueName));
                                    if (string.IsNullOrWhiteSpace(leagueName))
                                    {
                                        await CommandErrorLogRecord.LogCommandValidationError(commandGuid, COMMAND_NAME, true, "League name may not be blank");

                                        #region Logging
                                        if (null != log)
                                        {
                                            log.LogWarning($"Command {COMMAND_NAME } :: {commandId} has a blank league name in ValidateCreateLeagueCommand");
                                        }
                                        #endregion
                                    }
                                    else
                                    {
                                        leagueNameValid = true;

                                        // was the name already used?
                                        EventStream leagueEvents = new EventStream(@"Leagues",
                                                                                   "League",
                                                                                   leagueName);

                                        if (null != leagueEvents)
                                        {
                                            bool alreadyUsed = await leagueEvents.Exists();

                                            if (alreadyUsed)
                                            {
                                                await CommandErrorLogRecord.LogCommandValidationError(commandGuid,
                                                                                                      COMMAND_NAME, true,
                                                                                                      $"League name '{leagueName}' already created.");

                                                #region Logging
                                                if (null != log)
                                                {
                                                    log.LogWarning($"Command {COMMAND_NAME } :: {commandId} league name '{leagueName}' already created in ValidateCreateLeagueCommand");
                                                }
                                                #endregion

                                                leagueNameValid = false;
                                            }
                                        }
                                    }
                                }

                                // The incoporation date may not be in the future
                                if (cmdProjection.ParameterIsSet(nameof(ICreate_New_League_Definition.Date_Incorporated)))
                                {
                                    DateTime dateIncorporated = cmdProjection.GetParameter <DateTime>(nameof(ICreate_New_League_Definition.Date_Incorporated));
                                    if (dateIncorporated > DateTime.UtcNow)
                                    {
                                        await CommandErrorLogRecord.LogCommandValidationError(commandGuid, COMMAND_NAME, false, "Incorporation date is in the future");

                                        #region Logging
                                        if (null != log)
                                        {
                                            log.LogWarning($"Command {COMMAND_NAME } :: {commandId} has a future dated incorporation date in ValidateCreateLeagueCommand");
                                        }
                                        #endregion
                                    }
                                    else
                                    {
                                        incoporatedDateValid = true;
                                    }
                                }

                                if (incoporatedDateValid && leagueNameValid)
                                {
                                    await CommandErrorLogRecord.LogCommandValidationSuccess(commandGuid, COMMAND_NAME);
                                }

                                return(incoporatedDateValid && leagueNameValid);
                            }
                        }
                        else
                        {
                            // No events were read into the projection so do nothing
                            #region Logging
                            if (null != log)
                            {
                                log.LogWarning($"No command events read for {commandId} in ValidateCreateLeagueCommand");
                            }
                            #endregion
                        }
                    }
                }

                return(false);
            }
        }
        public static async Task <HttpResponseMessage> PayInterestRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"PayInterest/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(InterestDue))] Projection prjInterestDue,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance,
            [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                // You cannot pay interest if the account does not exist
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE));
            }

            // get the interest owed / due as now
            InterestDue interestDue = await prjInterestDue.Process <InterestDue>();

            if (null != interestDue)
            {
                // if the interest due is negative we need to make sure the account has sufficient balance
                if (interestDue.Due < 0.00M)
                {
                    Balance balance = await prjBankAccountBalance.Process <Balance>();

                    if (null != balance)
                    {
                        decimal availableBalance = balance.CurrentBalance;

                        // is there an overdraft?
                        OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>();

                        if (null != overdraft)
                        {
                            availableBalance += overdraft.CurrentOverdraftLimit;
                        }

                        if (availableBalance < interestDue.Due)
                        {
                            // can't pay the interest
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Unable to pay interest of {interestDue.Due} as available balance is only {availableBalance}",
                                                                                                                             interestDue.CurrentSequenceNumber),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }
                    }
                }

                // pay the interest
                decimal amountToPay = decimal.Round(interestDue.Due, 2, MidpointRounding.AwayFromZero);
                if (amountToPay != 0.00M)
                {
                    InterestPaid evInterestPaid = new InterestPaid()
                    {
                        AmountPaid = decimal.Round(interestDue.Due, 2, MidpointRounding.AwayFromZero),
                        Commentary = $"Interest due {interestDue.Due} as at {interestDue.CurrentSequenceNumber}"
                    };
                    await bankAccountEvents.AppendEvent(evInterestPaid);

                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     false,
                                                                                                                     evInterestPaid.Commentary,
                                                                                                                     interestDue.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }
                else
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     false,
                                                                                                                     $"No interest due so none was paid out",
                                                                                                                     interestDue.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }
            }
            else
            {
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Unable to get interest due for account {accountnumber} for interest payment",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE
                                                                       ));
            }
        }
        public static async Task <HttpResponseMessage> AccrueInterestRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"AccrueInterest/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance,
            [Classification("Bank", "Account", "{accountnumber}", nameof(InterestAccruedToday))] Classification clsAccruedToday)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                // You cannot accrue interest if the account does not exist
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0)));
            }

            ClassificationResponse isAccrued = await clsAccruedToday.Classify <InterestAccruedToday>();

            if (isAccrued.Result == ClassificationResponse.ClassificationResults.Include)
            {
                // The accrual for today has been performed for this account
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Interest accrual already done on account {accountnumber} today",
                                                                                                                 isAccrued.AsOfSequence),
                                                                       FunctionResponse.MEDIA_TYPE
                                                                       ));
            }


            // get the request body...
            InterestAccrualData data = await req.Content.ReadAsAsync <InterestAccrualData>();

            // Get the current account balance, as at midnight
            Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(DateTime.Today);

            if (null != projectedBalance)
            {
                Account.Events.InterestAccrued evAccrued = new Account.Events.InterestAccrued()
                {
                    Commentary           = data.Commentary,
                    AccrualEffectiveDate = DateTime.Today  // set the accrual to midnight today
                };

                if (projectedBalance.CurrentBalance >= 0)
                {
                    // Using the credit rate
                    evAccrued.AmountAccrued = data.CreditInterestRate * projectedBalance.CurrentBalance;
                }
                else
                {
                    // Use the debit rate
                    evAccrued.AmountAccrued = data.DebitInterestRate * projectedBalance.CurrentBalance;
                }

                try
                {
                    await bankAccountEvents.AppendEvent(evAccrued, isAccrued.AsOfSequence);
                }
                catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite)
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     true,
                                                                                                                     $"Failed to write interest accrual event {exWrite.Message}",
                                                                                                                     projectedBalance.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }

                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 false,
                                                                                                                 $"Interest accrued for account {accountnumber} is {evAccrued.AmountAccrued}",
                                                                                                                 projectedBalance.CurrentSequenceNumber),
                                                                       FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Unable to get current balance for account {accountnumber} for interest accrual",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE
                                                                       ));
            }
        }
        public static async Task <HttpResponseMessage> SetOverdraftLimitRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"SetOverdraftLimit/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                // You cannot set an overdraft if the account does not exist
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0)));
            }
            else
            {
                // get the request body...
                OverdraftSetData data = await req.Content.ReadAsAsync <OverdraftSetData>();

                // get the current account balance
                Balance projectedBalance = await prjBankAccountBalance.Process <Balance>();

                if (null != projectedBalance)
                {
                    if (projectedBalance.CurrentBalance >= (0 - data.NewOverdraftLimit))
                    {
                        // attempt to set the new overdraft limit
                        Account.Events.OverdraftLimitSet evOverdraftSet = new Account.Events.OverdraftLimitSet()
                        {
                            OverdraftLimit = data.NewOverdraftLimit,
                            Commentary     = data.Commentary
                        };
                        try
                        {
                            await bankAccountEvents.AppendEvent(evOverdraftSet, projectedBalance.CurrentSequenceNumber);
                        }
                        catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite)
                        {
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Failed to write overdraft limit event {exWrite.Message}",
                                                                                                                             projectedBalance.CurrentSequenceNumber),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }

                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         false,
                                                                                                                         $"{data.NewOverdraftLimit } set as the new overdraft limit for account {accountnumber}",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE));
                    }
                    else
                    {
                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         true,
                                                                                                                         $"Account {accountnumber} has an outstanding balance beyond the new limit {data.NewOverdraftLimit} (Current balance: {projectedBalance.CurrentBalance} )",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE
                                                                               ));
                    }
                }
                else
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     true,
                                                                                                                     $"Unable to get current balance for account {accountnumber}",
                                                                                                                     0),
                                                                           FunctionResponse.MEDIA_TYPE
                                                                           ));
                }
            }
        }
        public static async Task <HttpResponseMessage> OpenAccountRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"OpenAccount/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (await bankAccountEvents.Exists())
            {
                return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                             FunctionResponse.CreateResponse(startTime,
                                                                                             true,
                                                                                             $"Account {accountnumber} already exists"),
                                                             FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                // Get request body
                AccountOpeningData data = await req.Content.ReadAsAsync <AccountOpeningData>();

                // Append a "created" event
                DateTime dateCreated            = DateTime.UtcNow;
                Account.Events.Opened evtOpened = new Account.Events.Opened()
                {
                    LoggedOpeningDate = dateCreated
                };
                if (!string.IsNullOrWhiteSpace(data.Commentary))
                {
                    evtOpened.Commentary = data.Commentary;
                }
                try
                {
                    await bankAccountEvents.AppendEvent(evtOpened,
                                                        streamConstraint : EventStreamExistenceConstraint.MustBeNew
                                                        );
                }
                catch (EventStreamWriteException exWrite)
                {
                    return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.Conflict,
                                                                 FunctionResponse.CreateResponse(startTime,
                                                                                                 true,
                                                                                                 $"Account {accountnumber} had a conflict error on creation {exWrite.Message }"),
                                                                 FunctionResponse.MEDIA_TYPE));
                }

                // If there is an initial deposit in the account opening data, append a "deposit" event
                if (data.OpeningBalance.HasValue)
                {
                    Account.Events.MoneyDeposited evtInitialDeposit = new Account.Events.MoneyDeposited()
                    {
                        AmountDeposited   = data.OpeningBalance.Value,
                        LoggedDepositDate = dateCreated,
                        Commentary        = "Opening deposit"
                    };
                    await bankAccountEvents.AppendEvent(evtInitialDeposit);
                }

                // If there is a beneficiary in the account opening data append a "beneficiary set" event
                if (!string.IsNullOrEmpty(data.ClientName))
                {
                    Account.Events.BeneficiarySet evtBeneficiary = new Account.Events.BeneficiarySet()
                    {
                        BeneficiaryName = data.ClientName
                    };
                    await bankAccountEvents.AppendEvent(evtBeneficiary);
                }

                return(req.CreateResponse <FunctionResponse>(System.Net.HttpStatusCode.Created,
                                                             FunctionResponse.CreateResponse(startTime,
                                                                                             false,
                                                                                             $"Account { accountnumber} created"),
                                                             FunctionResponse.MEDIA_TYPE));
            }
        }
        public static async Task <HttpResponseMessage> WithdrawMoneyRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"WithdrawMoney/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance,
            [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.NotFound,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                // get the request body...
                MoneyWithdrawnData data = await req.Content.ReadAsAsync <MoneyWithdrawnData>();

                // get the current account balance
                Balance projectedBalance = await prjBankAccountBalance.Process <Balance>();

                if (null != projectedBalance)
                {
                    OverdraftLimit projectedOverdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>();

                    decimal overdraftSet = 0.00M;
                    if (null != projectedOverdraft)
                    {
                        if (projectedOverdraft.CurrentSequenceNumber != projectedBalance.CurrentSequenceNumber)
                        {
                            // The two projections are out of synch.  In a real business case we would retry them
                            // n times to try and get a match but here we will just throw a consistency error
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Unable to get a matching state for the current balance and overdraft for account {accountnumber}",
                                                                                                                             0),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }
                        else
                        {
                            overdraftSet = projectedOverdraft.CurrentOverdraftLimit;
                        }
                    }

                    if ((projectedBalance.CurrentBalance + overdraftSet) >= data.AmountWithdrawn)
                    {
                        // attempt the withdrawal
                        DateTime dateWithdrawn = DateTime.UtcNow;
                        Account.Events.MoneyWithdrawn evWithdrawn = new Account.Events.MoneyWithdrawn()
                        {
                            LoggedWithdrawalDate = dateWithdrawn,
                            AmountWithdrawn      = data.AmountWithdrawn,
                            Commentary           = data.Commentary
                        };
                        try
                        {
                            await bankAccountEvents.AppendEvent(evWithdrawn, projectedBalance.CurrentSequenceNumber);
                        }
                        catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite)
                        {
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Failed to write withdrawal event {exWrite.Message}",
                                                                                                                             0),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }

                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         false,
                                                                                                                         $"{data.AmountWithdrawn } withdrawn from account {accountnumber} (New balance: {projectedBalance.CurrentBalance - data.AmountWithdrawn}, overdraft: {overdraftSet} )",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE));
                    }
                    else
                    {
                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         true,
                                                                                                                         $"Account {accountnumber} does not have sufficent funds for the withdrawal of {data.AmountWithdrawn} (Current balance: {projectedBalance.CurrentBalance}, overdraft: {overdraftSet} )",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE));
                    }
                }
                else
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     true,
                                                                                                                     $"Unable to get current balance for account {accountnumber}",
                                                                                                                     projectedBalance.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }
            }
        }