public async Task Resolve <TEntity, TState>(TEntity entity, Guid commitId, IDictionary <string, string> commitHeaders) where TEntity : IEntity <TState> where TState : class, IState, new()
        {
            var state = entity.State;

            Logger.DebugEvent("Resolver", "Resolving {Events} conflicting events to stream [{Stream:l}] type [{EntityType:l}] bucket [{Bucket:l}]", entity.Uncommitted.Count(), entity.Id, typeof(TEntity).FullName, entity.Bucket);

            var domainEvents = entity.Uncommitted.Where(x => x.Descriptor.StreamType == StreamTypes.Domain).ToArray();
            var oobEvents    = entity.Uncommitted.Where(x => x.Descriptor.StreamType == StreamTypes.OOB).ToArray();

            await _store.WriteEvents <TEntity>(entity.Bucket, entity.Id, entity.GetParentIds(), domainEvents, commitHeaders).ConfigureAwait(false);

            await _oobStore.WriteEvents <TEntity>(entity.Bucket, entity.Id, entity.GetParentIds(), oobEvents, commitId, commitHeaders).ConfigureAwait(false);
        }
示例#2
0
        public async Task Commit <TEntity, TState>(TEntity entity, Guid commitId, IDictionary <string, string> commitHeaders) where TEntity : IEntity <TState> where TState : class, IState, new()
        {
            if (!entity.Dirty)
            {
                throw new ArgumentException($"Entity {typeof(TEntity).FullName} id {entity.Id} bucket {entity.Bucket} is not dirty");
            }

            var state = entity.State;

            var domainEvents = entity.Uncommitted.Where(x => x.Descriptor.StreamType == StreamTypes.Domain).ToArray();
            var oobEvents    = entity.Uncommitted.Where(x => x.Descriptor.StreamType == StreamTypes.OOB).ToArray();

            try
            {
                if (domainEvents.Any())
                {
                    await _eventstore.WriteEvents <TEntity>(entity.Bucket, entity.Id, entity.Parents,
                                                            domainEvents, commitHeaders, entity.Version).ConfigureAwait(false);
                }
            }
            catch (VersionException e)
            {
                Logger.DebugEvent("VersionConflict", "[{EntityId:l}] entity [{EntityType:l}] version {Version} commit version {CommitVersion} - {StoreMessage}", entity.Id, typeof(TEntity).FullName, state.Version, entity.Version, e.Message);
                _metrics.Mark("Conflicts", Unit.Items);
                // If we expected no stream, no reason to try to resolve the conflict
                if (entity.Version == EntityFactory.NewEntityVersion)
                {
                    Logger.DebugEvent("AlreadyExists", "[{EntityId:l}] entity [{EntityType:l}] already exists", entity.Id, typeof(TEntity).FullName);
                    throw new EntityAlreadyExistsException <TEntity>(entity.Bucket, entity.Id, entity.Parents);
                }

                try
                {
                    // Todo: cache per entity type
                    var conflictResolution = (OptimisticConcurrencyAttribute)Attribute.GetCustomAttribute(typeof(TEntity), typeof(OptimisticConcurrencyAttribute))
                                             ?? new OptimisticConcurrencyAttribute(ConcurrencyConflict.Throw);

                    Logger.DebugEvent("ConflictResolve", "[{EntityId:l}] entity [{EntityType:l}] resolving {ConflictingEvents} events with {ConflictResolver}", entity.Id, typeof(TEntity).FullName, entity.Uncommitted.Count(), conflictResolution.Conflict);
                    var strategy = conflictResolution.Conflict.Build(conflictResolution.Resolver);

                    commitHeaders[Defaults.ConflictResolvedHeader] = conflictResolution.Conflict.DisplayName;

                    await strategy.Resolve <TEntity, TState>(entity, commitId, commitHeaders).ConfigureAwait(false);

                    Logger.DebugEvent("ConflictResolveSuccess", "[{EntityId:l}] entity [{EntityType:l}] resolution success", entity.Id, typeof(TEntity).FullName);
                }
                catch (AbandonConflictException abandon)
                {
                    _metrics.Mark("Conflicts Unresolved", Unit.Items);
                    Logger.ErrorEvent("ConflictResolveAbandon", "[{EntityId:l}] entity [{EntityType:l}] abandonded", entity.Id, typeof(TEntity).FullName);

                    throw new ConflictResolutionFailedException(entity.GetType(), entity.Bucket, entity.Id, entity.Parents, "Aborted", abandon);
                }
                catch (Exception ex)
                {
                    _metrics.Mark("Conflicts Unresolved", Unit.Items);
                    Logger.ErrorEvent("ConflictResolveFail", ex, "[{EntityId:l}] entity [{EntityType:l}] failed: {ExceptionType} - {ExceptionMessage}", entity.Id, typeof(TEntity).FullName, ex.GetType().Name, ex.Message);

                    throw new ConflictResolutionFailedException(entity.GetType(), entity.Bucket, entity.Id, entity.Parents, "Exception", ex);
                }
            }
            catch (PersistenceException e)
            {
                Logger.WarnEvent("CommitFailure", e, "[{EntityId:l}] entity [{EntityType:l}] bucket [{Bucket:l}]: {ExceptionType} - {ExceptionMessage}", entity.Id, typeof(TEntity).Name, entity.Bucket, e.GetType().Name, e.Message);
                _metrics.Mark("Event Write Errors", Unit.Errors);
                throw;
            }

            try
            {
                if (oobEvents.Any())
                {
                    await _oobstore.WriteEvents <TEntity>(entity.Bucket, entity.Id, entity.Parents, oobEvents, commitId, commitHeaders).ConfigureAwait(false);
                }

                if (entity.State.ShouldSnapshot())
                {
                    // Notify the entity and state that we are taking a snapshot
                    (entity as IEntity <TState>).Snapshotting();
                    entity.State.Snapshotting();
                    await _snapstore.WriteSnapshots <TEntity>(entity.State, commitHeaders).ConfigureAwait(false);
                }
            }
            catch (Exception e)
            {
                Logger.WarnEvent("SecondaryFailure", "[{EntityId:l}] entity [{EntityType:l}] bucket [{Bucket:l}]: {ExceptionType} - {ExceptionMessage}", entity.Id, typeof(TEntity).Name, entity.Bucket, e.GetType().Name, e.Message);
            }
        }