Я приближаюсь к концу проекта, для которого я пытаюсь использовать DDD, но обнаружил явную ошибку, которую я не уверен, как легко решить.Метод рефакторинга сущности, чтобы избежать проблем с параллелизмом
Вот моя сущность - я свел его для простоты:
public class Contribution : Entity
{
protected Contribution()
{
this.Parts = new List<ContributionPart>();
}
internal Contribution(Guid id)
{
this.Id = id;
this.Parts = new List<ContributionPart>();
}
public Guid Id { get; private set; }
protected virtual IList<ContributionPart> Parts { get; private set; }
public void UploadParts(string path, IEnumerable<long> partLengths)
{
if (this.Parts.Count > 0)
{
throw new InvalidOperationException("Parts have already been uploaded.");
}
long startPosition = 0;
int partNumber = 1;
foreach (long partLength in partLengths)
{
this.Parts.Add(new ContributionPart(this.Id, partNumber, partLength));
this.Commands.Add(new UploadContributionPartCommand(this.Id, partNumber, path, startPosition, partLength));
startPosition += partLength;
partNumber++;
}
}
public void SetUploadResult(int partNumber, string etag)
{
if (etag == null)
{
throw new ArgumentNullException(nameof(etag));
}
ContributionPart part = this.Parts.SingleOrDefault(p => p.PartNumber == partNumber);
if (part == null)
{
throw new ContributionPartNotFoundException(this.Id, partNumber);
}
part.SetUploadResult(etag);
if (this.Parts.All(p => p.IsUploaded))
{
IEnumerable<PartUploadedResult> results = this.Parts.Select(p => new PartUploadedResult(p.PartNumber, p.ETag));
this.Events.Add(new ContributionUploaded(this.Id, results));
}
}
}
Моя ошибка происходит в методе SetUploadResult. В основном, несколько потоков выполняют одновременную загрузку, а затем вызывают SetUploadResult в конце загрузки. Но поскольку объект был загружен за несколько секунд до этого, каждый поток будет вызывать SetUploadResult на другом экземпляре объекта, поэтому тест if (this.Parts.All(p => p.IsUploaded)
никогда не будет оценивать true.
Я не уверен, как легко решить это. Идея добавления нескольких команд UploadContributionPartCommands в коллекцию Commands заключалась в том, чтобы каждый ContributionPart мог быть загружен параллельно - мой CommandBus обеспечивает это, но с каждой загруженной частью параллельно это вызывает проблемы для моей логики сущности.
Итак, вы говорите, что существует несколько потоков, работающих в одном экземпляре объекта Contribution? – mm8
Правильно. Объект вклада создал команду UploadContributionPartCommand для каждой части длины, и каждый UploadContributionPartCommandHandler выполняет параллель и, таким образом, вызывает вызовы SetUploadResult параллельно. Кроме того, это не тот же экземпляр в памяти объекта, но он является одним и тем же объектом. –
Каким образом это не тот же «экземпляр в памяти», поскольку каждый экземпляр имеет свои собственные части? – mm8