public object Lock(IAggregateRootId aggregateRootId, TimeSpan ttl) { if (ReferenceEquals(null, aggregateRootId)) throw new ArgumentNullException(nameof(aggregateRootId)); var lockresult = lockManager.Lock(aggregateRootId, ttl); return lockresult.LockAcquired ? lockresult.Mutex : null; }
public AggregateCommit(IAggregateRootId aggregateId, int revision, List<IEvent> events) { AggregateRootId = aggregateId.RawId; BoundedContext = aggregateId.GetType().GetBoundedContext().BoundedContextName; Revision = revision; InternalEvents = events.Cast<object>().ToList(); Timestamp = DateTime.UtcNow.ToFileTimeUtc(); }
internal protected List<AggregateCommit> Seek(IAggregateRootId aggregateId) { var idHash = Convert.ToBase64String(aggregateId.RawId); ConcurrentQueue<AggregateCommit> commits; if (eventsStreams.TryGetValue(idHash, out commits)) return commits.ToList(); else return new List<AggregateCommit>(); }
public Result<bool> Execute(IAggregateRootId aggregateRootId, int aggregateRootRevision, Action action) { var result = new Result<bool>(false); var acquired = new AtomicBoolean(false); try { if (aggregateLock.TryGetValue(aggregateRootId, out acquired) == false) { acquired = acquired ?? new AtomicBoolean(false); if (aggregateLock.TryAdd(aggregateRootId, acquired) == false) aggregateLock.TryGetValue(aggregateRootId, out acquired); } if (acquired.CompareAndSet(false, true)) { try { AtomicInteger revision = null; if (aggregateRevisions.TryGetValue(aggregateRootId, out revision) == false) { revision = new AtomicInteger(aggregateRootRevision - 1); if (aggregateRevisions.TryAdd(aggregateRootId, revision) == false) return result; } var currentRevision = revision.Value; if (revision.CompareAndSet(aggregateRootRevision - 1, aggregateRootRevision)) { try { action(); return Result.Success; } catch (Exception) { revision.GetAndSet(currentRevision); throw; } } } finally { acquired.GetAndSet(false); } } return result; } catch (Exception ex) { return result.WithError(ex); } }
private Result<object> Lock(IAggregateRootId arId, TimeSpan ttl) { object mutex; try { mutex = aggregateRootLock.Lock(arId, ttl); if (ReferenceEquals(null, mutex)) return new Result<object>().WithError("failed lock"); return new Result<object>(mutex); } catch (Exception ex) { return new Result<object>().WithError(ex); } }
public Result<bool> HasRevision(IAggregateRootId aggregateRootId) { if (ReferenceEquals(null, aggregateRootId)) throw new ArgumentNullException(nameof(aggregateRootId)); if (connection.IsConnected == false) return Result.Error($"Unreachable endpoint '{connection.ClientName}'."); var revisionKey = CreateRedisRevisionKey(aggregateRootId); try { var result = connection.GetDatabase().KeyExists(revisionKey); return new Result<bool>(result); } catch (Exception ex) { return Result.Error(ex); } }
public Result<bool> SaveRevision(IAggregateRootId aggregateRootId, int revision, TimeSpan? expiry) { if (ReferenceEquals(null, aggregateRootId)) throw new ArgumentNullException(nameof(aggregateRootId)); if (connection.IsConnected == false) return Result.Error($"Unreachable endpoint '{connection.ClientName}'."); var revisionKey = CreateRedisRevisionKey(aggregateRootId); try { var result = connection.GetDatabase().StringSet(revisionKey, string.Join(",", revision, DateTime.UtcNow), expiry); return new Result<bool>(result); } catch (Exception ex) { return Result.Error(ex); } }
public Result<int> GetRevision(IAggregateRootId aggregateRootId) { if (ReferenceEquals(null, aggregateRootId)) throw new ArgumentNullException(nameof(aggregateRootId)); if (connection.IsConnected == false) return new Result<int>().WithError($"Unreachable endpoint '{connection.ClientName}'."); var revisionKey = CreateRedisRevisionKey(aggregateRootId); try { var value = connection.GetDatabase().StringGet(revisionKey); if (value.HasValue == false) return new Result<int>().WithError($"Missing value for {revisionKey} '{connection.ClientName}'."); var revisionValue = ((string)value).Split(',').First(); return new Result<int>(int.Parse(revisionValue)); } catch (Exception ex) { return new Result<int>().WithError(ex); } }
public EventStream Load(IAggregateRootId aggregateId) { using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); string boundedContext = aggregateId.GetType().GetBoundedContext().BoundedContextName; string query = String.Format(LoadAggregateStateQueryTemplate, tableNameStrategy.GetEventsTableName(boundedContext, aggregateId)); SqlCommand command = new SqlCommand(query, connection); command.Parameters.AddWithValue("@aggregateId", Convert.ToBase64String(aggregateId.RawId)); List<AggregateCommit> aggregateCommits = new List<AggregateCommit>(); using (var reader = command.ExecuteReader()) { while (reader.Read()) { var buffer = reader[0] as byte[]; using (var stream = new MemoryStream(buffer)) { aggregateCommits.Add((AggregateCommit)serializer.Deserialize(stream)); } } } return new EventStream(aggregateCommits); } }
public Result<bool> Execute(IAggregateRootId arId, int aggregateRootRevision, Action action) { var lockResult = Lock(arId, options.LockTtl); if (lockResult.IsNotSuccessful) return Result.Error("lock failed"); try { if (CanExecuteAction(arId, aggregateRootRevision)) { var actionResult = ExecuteAction(action); if (actionResult.IsNotSuccessful) { Rollback(arId, aggregateRootRevision - 1); return Result.Error("action failed"); } PersistRevision(arId, aggregateRootRevision); return actionResult; } return Result.Error("unable to execute action"); } catch (Exception ex) { // TODO log return Result.Error(ex); } finally { Unlock(lockResult.Value); } }
public Result<bool> SaveRevision(IAggregateRootId aggregateRootId, int revision) { return SaveRevision(aggregateRootId, revision, null); }
public virtual bool Equals(IAggregateRootId other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return ByteArrayHelper.Compare(RawId, other.RawId); }
public bool IsLocked(IAggregateRootId aggregateRootId) { return lockManager.IsLocked(aggregateRootId); }
private void Rollback(IAggregateRootId arId, int revision) { revisionStore.SaveRevision(arId, revision, options.LongTtl); }
private bool CanExecuteAction(IAggregateRootId arId, int aggregateRootRevision) { try { var existingRevisionResult = CheckForExistingRevision(arId); if (existingRevisionResult.IsNotSuccessful) { return false; // TODO: log } if (existingRevisionResult.Value == false) { var prevRevResult = SavePreviouseRevison(arId, aggregateRootRevision); if (prevRevResult.IsNotSuccessful) return false; } var isConsecutiveRevision = IsConsecutiveRevision(arId, aggregateRootRevision); if (isConsecutiveRevision) { return IncrementRevision(arId, aggregateRootRevision).IsSuccessful; } return false; } catch (Exception) { return false; } }
private Result<bool> IncrementRevision(IAggregateRootId arId, int newRevision) { return revisionStore.SaveRevision(arId, newRevision, options.ShorTtl); }
private bool IsConsecutiveRevision(IAggregateRootId arId, int revision) { var storedRevisionResult = revisionStore.GetRevision(arId); return storedRevisionResult.IsSuccessful && storedRevisionResult.Value == revision - 1; }
private Result<bool> PersistRevision(IAggregateRootId arId, int revision) { return revisionStore.SaveRevision(arId, revision, options.LongTtl); }
private string GetRevisionKey(IAggregateRootId aggregateRootId) => $"cronus/{aggregateRootId.NID}/{Convert.ToBase64String(aggregateRootId.RawId)}";
protected override async Task <JobExecutionStatus> RunJobAsync(IClusterOperations cluster, CancellationToken cancellationToken = default) { // mynkow. this one fails IndexStatus indexStatus = await GetIndexStatusAsync <EventToAggregateRootId>().ConfigureAwait(false); if (indexStatus.IsNotPresent()) { return(JobExecutionStatus.Running); } //projectionStoreInitializer.Initialize(version); foreach (Type eventType in eventTypes.Items) { string eventTypeId = eventType.GetContractId(); bool hasMoreRecords = true; while (hasMoreRecords && Data.IsCompleted == false) { RebuildEventCounterIndex_JobData.EventTypeRebuildPaging paging = Data.EventTypePaging.Where(et => et.Type.Equals(eventTypeId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); string paginationToken = paging?.PaginationToken; if (string.IsNullOrEmpty(paginationToken)) { logger.Info(() => $"Message counter for {eventTypeId} has been reset"); // Maybe we should move this to a BeforeRun method. await messageCounter.ResetAsync(eventType).ConfigureAwait(false); } LoadIndexRecordsResult indexRecordsResult = await eventToAggregateIndex.EnumerateRecordsAsync(eventTypeId, paginationToken).ConfigureAwait(false); IEnumerable <IndexRecord> indexRecords = indexRecordsResult.Records; long currentSessionProcessedCount = 0; foreach (IndexRecord indexRecord in indexRecords) { currentSessionProcessedCount++; string mess = Encoding.UTF8.GetString(indexRecord.AggregateRootId); IAggregateRootId arId = GetAggregateRootId(mess); EventStream stream = await eventStore.LoadAsync(arId).ConfigureAwait(false); List <Task> incrementTasks = new List <Task>(); foreach (AggregateCommit arCommit in stream.Commits) { foreach (var @event in arCommit.Events) { if (cancellationToken.IsCancellationRequested) { logger.Info(() => $"Job has been cancelled."); return(JobExecutionStatus.Running); } if (eventTypeId.Equals(@event.GetType().GetContractId(), StringComparison.OrdinalIgnoreCase)) { incrementTasks.Add(messageCounter.IncrementAsync(eventType)); } } } await Task.WhenAll(incrementTasks).ConfigureAwait(false); } Data.MarkPaginationTokenAsProcessed(eventTypeId, indexRecordsResult.PaginationToken); Data = await cluster.PingAsync(Data, cancellationToken).ConfigureAwait(false); hasMoreRecords = indexRecordsResult.Records.Any(); } } Data.IsCompleted = true; Data = await cluster.PingAsync(Data).ConfigureAwait(false); logger.Info(() => $"The job has been completed."); return(JobExecutionStatus.Completed); }
/// <summary> /// Loads all the commits of an aggregate with the specified aggregate identifier. /// </summary> /// <param name="aggregateId">The aggregate identifier.</param> /// <returns></returns> public EventStream Load(IAggregateRootId aggregateId) { return new EventStream(eventStoreStorage.Seek(aggregateId)); }
private Result<bool> SavePreviouseRevison(IAggregateRootId arId, int revision) { return revisionStore.SaveRevision(arId, revision - 1, options.ShorTtl); }
public ReplayResult Rebuild(ProjectionVersion version, DateTime rebuildUntil) { if (ReferenceEquals(null, version)) { throw new ArgumentNullException(nameof(version)); } Type projectionType = version.ProjectionName.GetTypeByContract(); try { IndexStatus indexStatus = GetIndexStatus <EventToAggregateRootId>(); if (indexStatus.IsNotPresent() && IsNotSystemProjection(projectionType)) { return(ReplayResult.RetryLater($"The index is not present")); } if (IsVersionTrackerMissing() && IsNotSystemProjection(projectionType)) { return(ReplayResult.RetryLater($"Projection `{version}` still don't have present index.")); //WHEN TO RETRY AGAIN } if (HasReplayTimeout(rebuildUntil)) { return(ReplayResult.Timeout($"Rebuild of projection `{version}` has expired. Version:{version} Deadline:{rebuildUntil}.")); } var allVersions = GetAllVersions(version); if (allVersions.IsOutdatad(version)) { return(new ReplayResult($"Version `{version}` is outdated. There is a newer one which is already live.")); } if (allVersions.IsCanceled(version)) { return(new ReplayResult($"Version `{version}` was canceled.")); } DateTime startRebuildTimestamp = DateTime.UtcNow; int progressCounter = 0; logger.Info(() => $"Start rebuilding projection `{version.ProjectionName}` for version {version}. Deadline is {rebuildUntil}"); Dictionary <int, string> processedAggregates = new Dictionary <int, string>(); projectionStoreInitializer.Initialize(version); var projectionHandledEventTypes = GetInvolvedEvents(projectionType); foreach (var eventType in projectionHandledEventTypes) { logger.Info(() => $"Rebuilding projection `{version.ProjectionName}` for version {version} using eventType `{eventType}`. Deadline is {rebuildUntil}"); IEnumerable <IndexRecord> indexRecords = index.EnumerateRecords(eventType); foreach (IndexRecord indexRecord in indexRecords) { // TODO: (5) Decorator pattern which will give us the tracking progressCounter++; if (progressCounter % 1000 == 0) { logger.Info(() => $"Rebuilding projection {version.ProjectionName} => PROGRESS:{progressCounter} Version:{version} EventType:{eventType} Deadline:{rebuildUntil} Total minutes working:{(DateTime.UtcNow - startRebuildTimestamp).TotalMinutes}. logId:{Guid.NewGuid().ToString()} ProcessedAggregatesSize:{processedAggregates.Count}"); } int aggreagteRootIdHash = indexRecord.AggregateRootId.GetHashCode(); if (processedAggregates.ContainsKey(aggreagteRootIdHash)) { continue; } processedAggregates.Add(aggreagteRootIdHash, null); string mess = Encoding.UTF8.GetString(indexRecord.AggregateRootId); IAggregateRootId arId = GetAggregateRootId(mess); EventStream stream = eventStore.Load(arId); foreach (AggregateCommit arCommit in stream.Commits) { for (int i = 0; i < arCommit.Events.Count; i++) { IEvent theEvent = arCommit.Events[i].Unwrap(); if (projectionHandledEventTypes.Contains(theEvent.GetType().GetContractId())) // filter out the events which are not part of the projection { var origin = new EventOrigin(mess, arCommit.Revision, i, arCommit.Timestamp); projectionWriter.Save(projectionType, theEvent, origin, version); } } } } } logger.Info(() => $"Finish rebuilding projection `{projectionType.Name}` for version {version}. Deadline was {rebuildUntil}"); return(new ReplayResult()); } catch (Exception ex) { string message = $"Unable to replay projection. Version:{version} ProjectionType:{projectionType.FullName}"; logger.ErrorException(ex, () => message); return(new ReplayResult(message + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace)); } }
private string CreateRedisRevisionKey(IAggregateRootId aggregateRootId) { var stringRawId = Convert.ToBase64String(aggregateRootId.RawId); return string.Concat("revision-", stringRawId); }
private Result<bool> CheckForExistingRevision(IAggregateRootId arId) { return revisionStore.HasRevision(arId); }
public Task <ReadResult <AR> > LoadAsync <AR>(IAggregateRootId id) where AR : IAggregateRoot { return(aggregateRepository.LoadAsync <AR>(id)); }