Skip to content

Getting started

Duncan Jones edited this page Dec 10, 2019 · 4 revisions

Getting started

In order to use the "event sourcing backed entities" in your own Azure Functions you need to download and include the EventSourcingOnAzureFunctions.Common library and include a reference to it in your own functions application.

Wiring it up

In your function application you need to wire up the event stream and projection bindings so that they can be incorporated as parameters in your own functions. You do this in your web jobs startup code.

[assembly: WebJobsStartup(typeof(AzureFunctionApp.AzureFunctionAppStartup),
    "CQRS on Azure Demo")]
namespace RetailBank.AzureFunctionApp
{


    /// <summary>
    /// Start-up class to load all the dependency injection and startup configuration code
    /// </summary>
    public class AzureFunctionAppStartup
        : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {

            // Initialise the CQRS / event sourcing bindings
            CQRSAzureBindings.InitializeServices(builder.Services);

            // Add the standard (built-in) bindings 
            builder.AddBuiltInBindings();

            //Register any extensions for bindings
            builder.AddExtension<InjectConfiguration>();

        }
    }
}

Connecting to the underlying Azure storage accounts

The event streams are stored in blob storage in the Azure storage account(s) specified for them in your application configuration's ConnectionStrings properties. These application settings can be set through the settings.config file or via the Azure portal.

The connection strings are named [Domain].[EntityType].StorageConnectionString - so, for example, the account entities for a bank domain would use whatever storage is specified by the connection string Bank.Account.StorageConnectionString

Within that Azure storage account the data are stored in a folder structure :- /[domain]/[entity type]/eventstreams/ and each unique entity has its own blob in that location.

Designing events

In this system an event is expressed as a simple class with whatever properties you want to store in an instance of that event. There is also an attribute EventNameAttribute to allow you to mark this with a business meaningful event name. For example an event for money being deposited in an account might look like:

    /// <summary>
    /// Money was withdrawn from a customer account
    /// </summary>
    [EventName("Money Withdrawn")]
    public class MoneyWithdrawn
    {

        /// <summary>
        /// The amount of money to be withdrawn into the account
        /// </summary>
        public decimal AmountWithdrawn { get; set; }

        /// <summary>
        /// The commentary attached to the money withdrawal 
        /// </summary>
        public string Commentary { get; set; }

        /// <summary>
        /// Where did the withdrawal go to 
        /// </summary>
        public string Target { get; set; }

        /// <summary>
        /// If this withdrawal is part of a business transaction, this is the correlation identifier
        /// that links together all the component parts
        /// </summary>
        public string TransactionCorrelationIdentifier { get; set; }

        /// <summary>
        /// The date/time this withdrawal was recorded in our system
        /// </summary>
        public DateTime LoggedWithdrawalDate { get; set; }
    }

It is good practice to keep all the events that apply to any given entity type together in a folder.

Designing projections

A projection class must inherit from the class ProjectionBase and each type of event that it handles must be included as an implementation of the IHandleEventType<T> interface.

For example a projection to get the running balance of a bank account would need to handle the "money deposited" and "money withdrawn" events.

    /// <summary>
    /// The running balance of the account
    /// </summary>
    public class Balance
        : ProjectionBase,
        IHandleEventType<MoneyDeposited>,
        IHandleEventType<MoneyWithdrawn >
    {

        private decimal currentBalance;
        /// <summary>
        /// The current balance after the projection has run over a bank account event stream
        /// </summary>
        public decimal CurrentBalance
        {
            get
            {
                return currentBalance;
            }
        }



        public void HandleEventInstance(MoneyDeposited eventInstance)
        {
            if (null != eventInstance )
            {
                currentBalance += eventInstance.AmountDeposited;
            }
        }

        public void HandleEventInstance(MoneyWithdrawn eventInstance)
        {
            if (null != eventInstance )
            {
                currentBalance -= eventInstance.AmountWithdrawn;
            }
        }
    }

(In practice there are many more event types that would impact the account balance but this is a reasonable demo to start from)

Configuration settings

In order to persist the events (in an Event Stream ) the system needs to be configured with how you are going to store the data.

This is done in the application environment strings which can be set in the Azure portal.

[entity type key]=[storage type];[connection string name]

The setting entity type key is the domain name and entity type name to be stored using that setting, the storage type setting can be "AppendBlob" or "Table" and the connection string name is the name of the connection string to use to connect to the underlying storage.

In addition, if you want to use event names that are not your application class names you can map between them and the .NET class that implements them with the EventMaps section.

  "EventMaps": {
    "Designated Benificiary Set": "RetailBank.AzureFunctionApp.Account.Events.BeneficiarySet",
    "Money Deposited": "RetailBank.AzureFunctionApp.Account.Events.MoneyDeposited",
    "Money Withdrawn": "RetailBank.AzureFunctionApp.Account.Events.MoneyWithdrawn",
    "Account Opened": "RetailBank.AzureFunctionApp.Account.Events.AccountOpened"
  }

These can also be baked in at design time using the eventname attribute in your code.

Clone this wiki locally