Skip to content

6. Auditing and Event Sourcing

James Randall edited this page Dec 3, 2017 · 4 revisions

A benefit of invoking actions through commands expressed as state is that it allows for these actions to be persisted to maintain a log or audit of all the interactions that took place. Given an appropriate data storage and command handler design this further allows the persisted events to be utilised with patterns such as event sourcing or compensating transactions in order to deal with failed operations concerned with, for example, distributed none-transactional storage operations.

This makes the command pattern with a mediator particularly useful for distributed systems running in the cloud where such requirements are common.

The framework has extension points for auditors at three points in the command dispatch and execution process:

  1. Before the framework dispatches the command
  2. After the framework has successfully dispatched the command but before it has executed the command
  3. After the framework has executed the command (this also logs whether this completed successfully or encountered errors)

Auditors are required to implement the ICommandAuditor interface and are constructed by the commanding framework the first time they are used (and then held onto) through the IoC container and so can be injected with any registered dependencies. For example:

public class ConsoleAuditor : ICommandAuditor
{
    public Task Audit(AuditItem item, CancellationToken cancellationToken)
    {
        ConsoleColor previousColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.DarkGreen;
        Console.WriteLine($"Type: {item.CommandTypeFullName}");
        Console.WriteLine($"Correlation ID: {item.CorrelationId}");
        Console.WriteLine($"Depth: {item.Depth}");
        foreach (KeyValuePair<string, string> enrichedProperty in item.AdditionalProperties)
        {
            Console.WriteLine($"{enrichedProperty.Key}: {enrichedProperty.Value}");
        }
        Console.ForegroundColor = previousColor;
        return Task.FromResult(0);
    }        
}

Auditors must then be registered with the commanding system before any commands are dispatched:

ICommandingDependencyResolver resolver = ...;
ICommandRegistry registry = resolver.UseCommanding();
// register commands here
resolver.UsePreDispatchCommandingAuditor<ConsoleAuditor>();
resolver.UsePostDispatchCommandingAuditor<ConsoleAuditor>();
resolver.UseExecutionDispatchCommandingAuditor<ConsoleAuditor>();

By default auditors will only audit the root command i.e. if one command handler dispatches a command from within itself only the initial command will be audited. To change this behavior and audit all commands pass false to the registration methods, for example:

resolver.UsePreDispatchCommandingAuditor<ConsoleAuditor>(false);

Finally it's worth noting that multiple auditors can be configured for each event so it's possible to write the command to an event store and also log some basic details into a diagnostic log system.

Presupplied Auditors

The framework is supplied with a number of prebuilt auditors and these are described below.

Azure Event Hubs

By adding this package commands will be audited to an Azure event hub. First install the NuGet package:

Install-Package AzureFromTheTrenches.Commanding.AzureEventHub

Then register the auditor:

resolver.UseEventHubCommandAuditing("eventhubconnectionstring", "myhub");

For high volume systems a partition key provider can be supplied by implementing and supplying an instance of an IPartitionKeyProvider. And finally a parameter can be supplied of type AzureEventHubAuditorOptions that determines what audit events the auditor is bound to and whether each audit should only audit the root command (this option set defaults to all events and root command only). For example:

resolver.UseEventHubCommandAuditing("eventhubconnectionstring", "myhub", new MyPartitionKeyProvider(), new AzureEventHubAuditorOptions {
    UsePostDispatchAuditor = false
});

Azure Table Storage

By adding this package commands will be audited to Azure Table Storage. First install the NuGet package:

Install-Package AzureFromTheTrenches.Commanding.AzureStorage

Then register the auditor:

CloudStorageAccount account = ...;
resolver.UseAzureStorageCommandAuditing(cloudStorageAccount);

By default this will bind to all audit events and output audit details into two tables:

commandauditbydate
commandauditbycorrelationid

It's possible to also output the full command payload into blob storage by passing a blob container reference when configuring storage:

CloudStorageAccount account = ...;
CloudBlobContainer container = ...;
resolver.UseAzureStorageCommandAuditing(cloudStorageAccount, container);

Alternative storage strategies can be provided by supplying an implementation of the IStorageStrategy interface (the auditor comes with two strategies built in: SingleTableStrategy and TablePerDayStrategy). And finally a parameter can be supplied of type AzureStorageAuditorOptions that determines what audit events the auditor is bound to and whether each audit should only audit the root command (this option set defaults to all events and root command only). For example:

CloudStorageAccount account = ...;
CloudBlobContainer container = ...;
resolver.UseAzureStorageCommandAuditing(cloudStorageAccount, container, new TablePerDayStrategy(), new AzureStorageAuditorOptions  {
    UsePostDispatchAuditor = false
});