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); }
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); } }