This is a client side web application that sends commands to the Bank Account event sourcing demo application which uses Azure Tables as the storage mechanism for the event streams underlying each bank account.
CloudBank is a demo front end to show how to put a Blazor UI front end over Azure functions using Event Sourcing for the data storage
This client side web application sends commands to the Bank Account event sourcing demo application which uses Azure Tables as the storage mechanism for the event streams underlying each bank account.
You can try it out from any modern web browser at this URL
Using the application
When the application is launched the front screen as above is shown, with the navigation bar down the left hand side. This is pretty much an out of the box standard Blazor site template.
Creating a new account
Click on the User menu to go to the form that allows you to create a new bank account. You can either type your own choice of bank account number of select the generate random account number button to have the system create one.
Once you have created a new account you can navigate to it with the navigate to {accountnumber} button.
Getting the account balance
To get the account balance select the first section and it will open up and the button get balance can be used to trigger the Azure function that gets the balance for this bank account.
Because the backing store is event sourced it is just as easy to get the state as at some past date as it is to get the current state so you can enter an as of date and the system will return the account balance as at that date.
Making a deposit
The second expandable section allows you to make a deposit.
There are parameters to fill in for the deposit amount, deposit source and any commentary and then pressing the submit button will pass these to the Azure function to make the deposit.
When a the deposit is made a message will indicate this.
Making a withdrawal
The third expandable section is for making a withdrawal.
This has parameters for ammount and for commentary and again pressing the submit button sends these parameters to the Azure function.
Because the fuunction to make a withdrawal needs to run a projection that checks the current balance (to prevent the account being overdrawn beyond its limit) this function also returns information about the account balance.
Setting an overdraft
The lowest of the expandable section allows for an overdraft to be set.
Pressing the submit button having filled in all the parameters will pass these to the Azure function to execute them.
Again, because this Azure function has to get the existing overdraft and balance for its validation it can return this information to the front end.
For every Azure function executed the system will also return the total time it took to execute the function and if the function also runs any projections over the event stream it will also return the sequence number of the last event read.
How it works
The back end of this application is running as Azure serverless functions which are backed by event sourcing with the event streams for each account held in an Azure table. This is described in this GitHub project
On the front end the Razor pages inject the standard http client and a RetailBankApi class which is used for all comminication with the Azure functions:
@using CloudBank.Services
@page <span class="pl-pds">"/myaccount"</span>
@page <span class="pl-pds">"/myaccount/{accountnumber}"</span>
@inject HttpClient Http
@inject IRetailBankApi retailBankApi
This RetailBankApi class is injected into every page by use of dependency injection in the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
<span class="pl-c">
services.AddBlazoredModal();
<span class="pl-c">
services.AddScoped <IRetailBankApi, RetailBankApiNoHttp>();
}
Behind the scenes the RetailBankApiNoHttp class loads the set of Azure functions from a configuration file called bank-api.json (which allows the application to have its back end changed without impacting the deployed front end) - this is loaded in the MainLayout.razor file:
@code
{
[Parameter]
public ApiCommand[] apiCommands { get; set; }
protected override async Task OnInitializedAsync()
{
if (null == apiCommands)
{
apiCommands = await Http.GetJsonAsync<ApiCommand[]>(<span class="pl-pds">"sample-data/bank-api.json"</span>);
if (null != apiCommands)
{
<span class="pl-c">
retailBankApi.Initialise(apiCommands);
}
}
}
}
To call these from the account actions razore page we have action code wired up for the submit button like:
<button @onclick=<span class="pl-pds">"@(() => GetBalance())"</span> class=<span class="pl-pds">"btn btn-secondary"</span>>
<span class=<span class="pl-pds">"oi oi-cloud-download"</span> aria-hidden=<span class="pl-pds">"true"</span>></span>
Get Balance
</button>
When triggered this calls the Azure function thus:
private async Task GetBalance()
{
LastFunctionMessage = <span class="pl-pds">$"Getting balance for {accountnumber} at {getbalancePayload.AsOfDate} "</span>;
try
{
var result = await retailBankApi.GetAccountBalance(Http, accountnumber, getbalancePayload);
LastFunctionMessage = result.Message;
LastRunTime = result.ExecutionTime;
LastSequenceNumber = result.SequenceNumber;
}
catch (Exception ex)
{
LastFunctionMessage = ex.Message;
}
}
This sends the command to the Azure function by http:
<span class="pl-c"></span><span class="pl-c"></span><span class="pl-c"></span>public async Task<ProjectionFunctionResponse> GetAccountBalance(HttpClient httpclient, string accountnumber, GetBalanceData payload)
{
if (null != httpclient)
{
string key = <span class="pl-pds">""</span>;
key = _commands.FirstOrDefault(f => f.CommandName == <span class="pl-pds">"Get Balance"</span>)?.ApiKey;
HttpRequestMessage message = new HttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(RetailBankApi.GetBase(), RetailBankApi.GetBalance(accountnumber, payload.AsOfDate , key))
};
var response = await httpclient.SendAsync(message);
if (response.IsSuccessStatusCode)
{
var jsonString= await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ProjectionFunctionResponse>(jsonString);
}
else
{
return new ProjectionFunctionResponse() { Message = <span class="pl-pds">$"Unable to get balance - {response.StatusCode}"</span>, InError = true };
}
}
return new ProjectionFunctionResponse() { Message = <span class="pl-pds">$"Not connected to retail bank API"</span>, InError = true };
}
Deployment
Because this Blazor front end runs entirely on the client it can be hosted on a static website - and the static website functionality that you get with an Azure storage account is perfect for this.
You will need to add this static website URL to the CORS settings of your Azure functions application so that it is allowed to be accessed from there: