private async Task UpdateReadModelAsync( ReadModelDescription readModelDescription, ReadModelUpdate readModelUpdate, IReadModelContext readModelContext, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelEnvelope <TReadModel> > > updateReadModel, CancellationToken cancellationToken) { var response = await _elasticClient.GetAsync <TReadModel>( readModelUpdate.ReadModelId, d => d .RequestConfiguration(c => c .AllowedStatusCodes((int)HttpStatusCode.NotFound)) .Index(readModelDescription.IndexName.Value), cancellationToken) .ConfigureAwait(false); var readModelEnvelope = response.Found ? ReadModelEnvelope <TReadModel> .With(readModelUpdate.ReadModelId, response.Source, response.Version) : ReadModelEnvelope <TReadModel> .Empty(readModelUpdate.ReadModelId); readModelEnvelope = await updateReadModel( readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken) .ConfigureAwait(false); if (readModelContext.IsMarkedForDeletion) { await DeleteAsync(readModelUpdate.ReadModelId, cancellationToken); return; } try { await _elasticClient.IndexAsync( readModelEnvelope.ReadModel, d => { d = d .RequestConfiguration(c => c) .Id(readModelUpdate.ReadModelId) .Index(readModelDescription.IndexName.Value); d = response.Found ? d.VersionType(VersionType.ExternalGte).Version(readModelEnvelope.Version.GetValueOrDefault()) : d.OpType(OpType.Create); return(d); }, cancellationToken) .ConfigureAwait(false); } catch (ElasticsearchClientException e) when(e.Response?.HttpStatusCode == (int)HttpStatusCode.Conflict) { throw new OptimisticConcurrencyException( $"Read model '{readModelUpdate.ReadModelId}' updated by another", e); } }
private async Task UpdateReadModelAsync(ReadModelDescription readModelDescription, ReadModelUpdate readModelUpdate, IReadModelContextFactory readModelContextFactory, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelUpdateResult <TReadModel> > > updateReadModel, CancellationToken cancellationToken) { var collection = _mongoDatabase.GetCollection <TReadModel>(readModelDescription.RootCollectionName.Value); var filter = Builders <TReadModel> .Filter.Eq(readmodel => readmodel.Id, readModelUpdate.ReadModelId); var result = collection.Find(filter).FirstOrDefault(); var isNew = result == null; var readModelEnvelope = !isNew ? ReadModelEnvelope <TReadModel> .With(readModelUpdate.ReadModelId, result) : ReadModelEnvelope <TReadModel> .Empty(readModelUpdate.ReadModelId); var readModelContext = readModelContextFactory.Create(readModelUpdate.ReadModelId, isNew); var readModelUpdateResult = await updateReadModel(readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken).ConfigureAwait(false); if (!readModelUpdateResult.IsModified) { return; } if (readModelContext.IsMarkedForDeletion) { await DeleteAsync(readModelUpdate.ReadModelId, cancellationToken); return; } readModelEnvelope = readModelUpdateResult.Envelope; var originalVersion = readModelEnvelope.ReadModel.Version; readModelEnvelope.ReadModel.Version = readModelEnvelope.Version; try { await collection.ReplaceOneAsync <TReadModel>( x => x.Id == readModelUpdate.ReadModelId && x.Version == originalVersion, readModelEnvelope.ReadModel, new UpdateOptions() { IsUpsert = true }, cancellationToken); } catch (MongoWriteException e) { throw new OptimisticConcurrencyException( $"Read model '{readModelUpdate.ReadModelId}' updated by another", e); } }
private async Task UpdateReadModelAsync(ReadModelDescription readModelDescription, ReadModelUpdate readModelUpdate, IReadModelContextFactory readModelContextFactory, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelUpdateResult <TReadModel> > > updateReadModel, CancellationToken cancellationToken) { var collection = _mongoDatabase.GetCollection <TReadModel>(readModelDescription.RootCollectionName.Value); var filter = Builders <TReadModel> .Filter.Eq(readmodel => readmodel._id, readModelUpdate.ReadModelId); var result = collection.Find(filter).FirstOrDefault(); var isNew = result == null; var readModelEnvelope = !isNew ? ReadModelEnvelope <TReadModel> .With(readModelUpdate.ReadModelId, result) : ReadModelEnvelope <TReadModel> .Empty(readModelUpdate.ReadModelId); var readModelContext = readModelContextFactory.Create(readModelUpdate.ReadModelId, isNew); var readModelUpdateResult = await updateReadModel(readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken).ConfigureAwait(false); if (!readModelUpdateResult.IsModified) { return; } readModelEnvelope = readModelUpdateResult.Envelope; readModelEnvelope.ReadModel._version = readModelEnvelope.Version; await collection.ReplaceOneAsync <TReadModel>( x => x._id == readModelUpdate.ReadModelId, readModelEnvelope.ReadModel, new UpdateOptions() { IsUpsert = true }, cancellationToken); }
private async Task UpdateReadModelAsync(TDbContext dbContext, IReadModelContextFactory readModelContextFactory, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelUpdateResult <TReadModel> > > updateReadModel, CancellationToken cancellationToken, ReadModelUpdate readModelUpdate) { var readModelId = readModelUpdate.ReadModelId; var readModelEnvelope = await GetAsync(dbContext, readModelId, cancellationToken, true) .ConfigureAwait(false); var entity = readModelEnvelope.ReadModel; var isNew = entity == null; if (entity == null) { entity = await _readModelFactory.CreateAsync(readModelId, cancellationToken).ConfigureAwait(false); readModelEnvelope = ReadModelEnvelope <TReadModel> .With(readModelId, entity); } var readModelContext = readModelContextFactory.Create(readModelId, isNew); var originalVersion = readModelEnvelope.Version; var updateResult = await updateReadModel( readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken) .ConfigureAwait(false); if (!updateResult.IsModified) { return; } if (readModelContext.IsMarkedForDeletion) { await DeleteAsync(dbContext, readModelId, cancellationToken).ConfigureAwait(false); return; } readModelEnvelope = updateResult.Envelope; entity = readModelEnvelope.ReadModel; var descriptor = GetDescriptor(dbContext); var entry = isNew ? dbContext.Add(entity) : dbContext.Entry(entity); descriptor.SetId(entry, readModelId); descriptor.SetVersion(entry, originalVersion, readModelEnvelope.Version); try { await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } catch (DbUpdateConcurrencyException e) { var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken) .ConfigureAwait(false); entry.CurrentValues.SetValues(databaseValues); throw new OptimisticConcurrencyException(e.Message, e); } Log.Verbose(() => $"Updated Entity Framework read model {typeof(TReadModel).PrettyPrint()} with ID '{readModelId}' to version '{readModelEnvelope.Version}'"); }
private async Task UpdateReadModelAsync( IReadModelContextFactory readModelContextFactory, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelUpdateResult <TReadModel> > > updateReadModel, CancellationToken cancellationToken, ReadModelUpdate readModelUpdate) { IMssqlReadModel mssqlReadModel; var readModelId = readModelUpdate.ReadModelId; var readModelEnvelope = await GetAsync(readModelId, cancellationToken).ConfigureAwait(false); var readModel = readModelEnvelope.ReadModel; var isNew = readModel == null; if (readModel == null) { readModel = await _readModelFactory.CreateAsync(readModelId, cancellationToken) .ConfigureAwait(false); mssqlReadModel = readModel as IMssqlReadModel; if (mssqlReadModel != null) { mssqlReadModel.AggregateId = readModelId; mssqlReadModel.CreateTime = readModelUpdate.DomainEvents.First().Timestamp; } readModelEnvelope = ReadModelEnvelope <TReadModel> .With(readModelUpdate.ReadModelId, readModel); } var readModelContext = readModelContextFactory.Create(readModelId, isNew); var originalVersion = readModelEnvelope.Version; var readModelUpdateResult = await updateReadModel( readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken) .ConfigureAwait(false); if (!readModelUpdateResult.IsModified) { return; } readModelEnvelope = readModelUpdateResult.Envelope; if (readModelContext.IsMarkedForDeletion) { await DeleteAsync(readModelId, cancellationToken).ConfigureAwait(false); return; } mssqlReadModel = readModel as IMssqlReadModel; if (mssqlReadModel != null) { mssqlReadModel.UpdatedTime = DateTimeOffset.Now; mssqlReadModel.LastAggregateSequenceNumber = (int)readModelEnvelope.Version.GetValueOrDefault(); } else { SetVersion(readModel, (int?)readModelEnvelope.Version); } var sql = isNew ? _readModelSqlGenerator.CreateInsertSql <TReadModel>() : _readModelSqlGenerator.CreateUpdateSql <TReadModel>(); var dynamicParameters = new DynamicParameters(readModel); if (originalVersion.HasValue) { dynamicParameters.Add("_PREVIOUS_VERSION", (int)originalVersion.Value); } var rowsAffected = await _connection.ExecuteAsync( Label.Named("mssql-store-read-model", ReadModelNameLowerCase), cancellationToken, sql, dynamicParameters).ConfigureAwait(false); if (rowsAffected != 1) { throw new OptimisticConcurrencyException( $"Read model '{readModelEnvelope.ReadModelId}' updated by another"); } Log.Verbose(() => $"Updated MSSQL read model {typeof(TReadModel).PrettyPrint()} with ID '{readModelId}' to version '{readModelEnvelope.Version}'"); }
private async Task UpdateReadModelAsync( IReadModelContext readModelContext, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelEnvelope <TReadModel> > > updateReadModel, CancellationToken cancellationToken, ReadModelUpdate readModelUpdate) { var readModelNameLowerCased = typeof(TReadModel).Name.ToLowerInvariant(); var readModelEnvelope = await GetAsync(readModelUpdate.ReadModelId, cancellationToken).ConfigureAwait(false); var readModel = readModelEnvelope.ReadModel; var isNew = readModel == null; if (readModel == null) { readModel = await _readModelFactory.CreateAsync(readModelUpdate.ReadModelId, cancellationToken).ConfigureAwait(false); readModelEnvelope = ReadModelEnvelope <TReadModel> .With(readModelUpdate.ReadModelId, readModel); } var originalVersion = readModelEnvelope.Version; readModelEnvelope = await updateReadModel( readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken) .ConfigureAwait(false); if (readModelContext.IsMarkedForDeletion) { await DeleteAsync(readModelUpdate.ReadModelId, cancellationToken); return; } SetVersion(readModel, (int?)readModelEnvelope.Version); SetIdentity(readModel, readModelEnvelope.ReadModelId); var sql = isNew ? _readModelSqlGenerator.CreateInsertSql <TReadModel>() : _readModelSqlGenerator.CreateUpdateSql <TReadModel>(); var dynamicParameters = new DynamicParameters(readModel); if (originalVersion.HasValue) { dynamicParameters.Add("_PREVIOUS_VERSION", (int)originalVersion.Value); } var rowsAffected = await _connection.ExecuteAsync( Label.Named("sql-store-read-model", readModelNameLowerCased), cancellationToken, sql, dynamicParameters) .ConfigureAwait(false); if (rowsAffected != 1) { throw new OptimisticConcurrencyException( $"Read model '{readModelEnvelope.ReadModelId}' updated by another"); } Log.Verbose(() => $"Updated SQL read model {typeof(TReadModel).PrettyPrint()} with ID '{readModelUpdate.ReadModelId}' to version '{readModelEnvelope.Version}'"); }
private async Task UpdateReadModelAsync( IReadModelContextFactory readModelContextFactory, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelUpdateResult <TReadModel> > > updateReadModel, CancellationToken cancellationToken, ReadModelUpdate readModelUpdate) { var readModelId = readModelUpdate.ReadModelId; var readModelEnvelope = await GetAsync(readModelId, cancellationToken).ConfigureAwait(false); var readModel = readModelEnvelope.ReadModel; var isNew = readModel == null; if (readModel == null) { readModel = await _readModelFactory.CreateAsync(readModelId, cancellationToken).ConfigureAwait(false); readModelEnvelope = ReadModelEnvelope <TReadModel> .With(readModelUpdate.ReadModelId, readModel); } var readModelContext = readModelContextFactory.Create(readModelId, isNew); var originalVersion = readModelEnvelope.Version; var readModelUpdateResult = await updateReadModel( readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken) .ConfigureAwait(false); if (!readModelUpdateResult.IsModified) { return; } readModelEnvelope = readModelUpdateResult.Envelope; if (readModelContext.IsMarkedForDeletion) { await DeleteAsync(readModelId, cancellationToken).ConfigureAwait(false); return; } var readModelType = typeof(TReadModel); var stream = readModelId == "null" ? readModelType.Name.ToLowerInvariant() : $"{readModelType.Name.ToLowerInvariant()}-{readModelId}"; var messageInput = new MessageInput { ID = Guid.NewGuid().ToString(), Type = $"{readModelType.Name}.{readModelEnvelope.Version}", Value = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(readModelEnvelope.ReadModel)) }; try { if (readModelUpdate is CursorBasedReadModelUpdate cursorBasedReadModelUpdate) { var cursorsMessageInput = new MessageInput { ID = Guid.NewGuid().ToString(), Type = "cursors", Value = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(cursorBasedReadModelUpdate.Cursors)) }; await _client.DB().AppendStreams( new StreamInput(cursorBasedReadModelUpdate.CursorsStream, new List <MessageInput> { cursorsMessageInput }), new StreamInput(stream, ConcurrencyCheck.ExpectStreamVersion(originalVersion.GetValueOrDefault()), new List <MessageInput> { messageInput }) ).ConfigureAwait(false); } else { await _client.DB().AppendStream(stream, ConcurrencyCheck.ExpectStreamVersion(originalVersion.GetValueOrDefault()), messageInput).ConfigureAwait(false); } } catch (Exception e) { throw new OptimisticConcurrencyException($"Read model '{readModelEnvelope.ReadModelId}' updated by another", e); } Log.Verbose(() => $"Updated StreamsDB read model {typeof(TReadModel).PrettyPrint()} with ID '{readModelId}' to version '{readModelEnvelope.Version}'"); }
private async Task UpdateReadModelAsync(ReadModelDescription readModelDescription, ReadModelUpdate readModelUpdate, IReadModelContextFactory readModelContextFactory, Func <IReadModelContext, IReadOnlyCollection <IDomainEvent>, ReadModelEnvelope <TReadModel>, CancellationToken, Task <ReadModelUpdateResult <TReadModel> > > updateReadModel, CancellationToken cancellationToken) { var readModelContext = readModelContextFactory.Create(readModelUpdate.ReadModelId, true); var collection = _mongoDatabase.GetCollection <TReadModel>(readModelDescription.RootCollectionName.Value); var readModelEnvelope = ReadModelEnvelope <TReadModel> .Empty(readModelUpdate.ReadModelId); var modelUpdateResult = await updateReadModel(readModelContext, readModelUpdate.DomainEvents, readModelEnvelope, cancellationToken).ConfigureAwait(false); readModelEnvelope = modelUpdateResult.Envelope; readModelEnvelope.ReadModel._id = ObjectIdGenerator.Instance.GenerateId(collection, readModelEnvelope.ReadModel); await collection.InsertOneAsync( readModelEnvelope.ReadModel, new InsertOneOptions { BypassDocumentValidation = true }, cancellationToken); }