public async Task SaveAsync(AggregateRoot aggregate)
        {
            Logger.Log($"Preparing to save aggregate {aggregate.Id} to DynamoDB");

            var uncommittedChanges = aggregate.GetUncommittedChanges();
            var version            = aggregate.Version;

            foreach (var @event in uncommittedChanges)
            {
                @event.Version   = ++version;
                @event.Timestamp = DateTime.UtcNow;

                // Write event to DynamoDB
                try
                {
                    var eventTable = Table.LoadTable(_dynamoDbClient, _tableName);

                    var record = new Document();
                    var prop   = @event.GetType().GetMembers().Where(m => m.MemberType == MemberTypes.Property).ToList();
                    foreach (var p in prop)
                    {
                        var name = p.Name;
                        // There should only be 4 fields in an Event - Id, Version, EventName, and Event
                        if (p.Name != nameof(Event.Id) && p.Name != nameof(Event.Version) && p.Name != nameof(Event.EventName))
                        {
                            name         = nameof(Event);
                            record[name] = JsonConvert.SerializeObject(@event);
                        }
                        else
                        {
                            record[name] = JsonConvert.SerializeObject(@event.GetType().GetProperty(name).GetValue(@event, null));
                        }
                    }

                    Logger.Log($"Saving aggregate to DynamoDB");
                    await eventTable.PutItemAsync(record);

                    Logger.Log($"Successfully saved aggregate to DynamoDB");
                }
                catch (Exception ex)
                {
                    // TODO Surface exception up
                    Logger.Log($"ERROR: {ex.Message}");
                    throw new Exception(ex.Message);
                }

                Logger.Log($"Checking if snapshots should be saved: {version > 2 && version % 3 == 0}");

                // Save a snapshot every 3 events
                if (version > 2 && version % 3 == 0)
                {
                    var originator = (ISnapshot)aggregate;

                    var snapshot = originator.GetSnapshot();
                    snapshot.Timestamp = DateTime.UtcNow;

                    await SaveSnapshotAsync(snapshot);
                }
            }
        }
        private async Task <QueryResponse> GetByIdAsync(Guid id)
        {
            Logger.Log($"Scanning DynamoDB for aggregate: {id}");

            // Read events from DynamoDB
            var request = new QueryRequest
            {
                TableName = _tableName,
                KeyConditionExpression    = "Id = :v_Id and Version > :v_Version",
                ExpressionAttributeValues = new Dictionary <string, AttributeValue> {
                    { ":v_Id", new AttributeValue {
                          S = $"\"{id.ToString()}\""
                      } },
                    { ":v_Version", new AttributeValue {
                          S = "0"
                      } }
                }
            };

            var response = await _dynamoDbClient.QueryAsync(request);

            Logger.Log($"Found items: {response.Items.Count}");

            return(response);
        }
        public async Task SaveSnapshotAsync(Snapshot snapshot)
        {
            try
            {
                var eventTable = Table.LoadTable(_dynamoDbClient, _tableName);

                var record = new Document
                {
                    ["Id"]        = JsonConvert.SerializeObject(snapshot.Id),
                    ["Version"]   = "Snapshot-" + (snapshot.Version + 1),
                    ["Event"]     = JsonConvert.SerializeObject(snapshot),
                    ["EventName"] = "Snapshot"
                };

                Logger.Log("Saving snapshot to DynamoDB");
                await eventTable.PutItemAsync(record);

                Logger.Log("Successfully saved snapshot to DynamoDB");
            }
            catch (Exception ex)
            {
                // TODO Surface exception up
                Logger.Log($"ERROR: {ex.Message}");
                throw new Exception(ex.Message);
            }
        }
        // TODO Get up to most recent snapshot and any newer events only
        private IEnumerable <Event> GetEventsFromDynamoDbResponse(IEnumerable <Dictionary <string, AttributeValue> > items)
        {
            var events = new List <Event>();

            Logger.Log($"Getting events from {items.Count()} items");

            foreach (var item in items)
            {
                var eventType = item.FirstOrDefault(i => i.Key == nameof(Event.EventName)).Value.S.Trim('"');
                Logger.Log($"Item EventType: {eventType}");
                var type = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()).First(x => x.Name == eventType);
                Logger.Log($"Item Type: {type}");

                if (type == typeof(Snapshot))
                {
                    continue;
                }

                var @event = JsonConvert.DeserializeObject(item.FirstOrDefault(i => i.Key == nameof(Event)).Value.S, type);
                Logger.Log($"Item Event: {@event}");

                events.Add((Event)@event);
            }

            return(events);
        }
        private async Task CreateDynamoDbTableAsync(string tableName)
        {
            var response = new CreateTableResponse();

            try
            {
                var request = new CreateTableRequest
                {
                    TableName            = tableName,
                    AttributeDefinitions = new List <AttributeDefinition>
                    {
                        new AttributeDefinition
                        {
                            AttributeName = "Id",
                            AttributeType = "S"
                        },
                        new AttributeDefinition
                        {
                            AttributeName = "Version",
                            AttributeType = "S"
                        }
                    },
                    KeySchema = new List <KeySchemaElement>
                    {
                        new KeySchemaElement {
                            AttributeName = "Id", KeyType = KeyType.HASH
                        },
                        new KeySchemaElement {
                            AttributeName = "Version", KeyType = KeyType.RANGE
                        }
                    },
                    ProvisionedThroughput = new ProvisionedThroughput
                    {
                        ReadCapacityUnits  = 5,
                        WriteCapacityUnits = 5
                    },
                    StreamSpecification = new StreamSpecification
                    {
                        StreamViewType = StreamViewType.NEW_IMAGE,
                        StreamEnabled  = true
                    }
                };

                var createTableResponse = _dynamoDbClient.CreateTableAsync(request);
                response = await createTableResponse;
            }
            catch (Exception ex)
            {
                Logger.Log($"ERROR: Failed to create the new table, because: {ex.Message}");
            }

            await WaitTillTableCreatedAsync(tableName, response);
        }
        private async Task DeleteDynamoDbTableAsync(string tableName)
        {
            var response = new DeleteTableResponse();

            try
            {
                var deleteResponse = _dynamoDbClient.DeleteTableAsync(tableName);
                response = await deleteResponse;
            }
            catch (Exception ex)
            {
                Logger.Log($"ERROR: Failed to delete the table, because:\n {ex.Message}");
            }

            await WaitTillTableDeletedAsync(tableName, response);
        }