Ich bin kurz vor dem Ende eines Projekts, für das ich versuche, DDD zu verwenden, habe aber einen eklatanten Fehler entdeckt, von dem ich nicht weiß, wie ich ihn leicht lösen kann.Refactoring-Entity-Methode zur Vermeidung von Nebenläufigkeitsproblemen
Hier ist mein Wesen - ich es der Einfachheit halber reduziert haben:
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));
}
}
}
Mein Fehler in der SetUploadResult Verfahren auftritt. Grundsätzlich führen mehrere Threads gleichzeitig Uploads durch und rufen dann am Ende des Uploads SetUploadResult auf. Da die Entität jedoch einige Sekunden zuvor geladen wurde, ruft jeder Thread SetUploadResult für eine andere Instanz der Entität auf, sodass der Test if (this.Parts.All(p => p.IsUploaded)
niemals als wahr ausgewertet wird.
Ich bin mir nicht sicher, wie man das leicht löst. Die Idee hinter dem Hinzufügen von mehreren UploadContributionPartCommands zur Commands-Sammlung war, dass jedes ContributionPart parallel hochgeladen werden kann - mein CommandBus stellt dies sicher - aber mit jedem parallel hochgeladenen Teil verursacht es Probleme für meine Entitätslogik.
Sie sagen also, dass mehrere Threads auf derselben Instanz einer Contribution-Entität arbeiten? – mm8
Korrekt. Die Beitragseinheit hat einen UploadContributionPartCommand für jede partLength erstellt, und jeder UploadContributionPartCommandHandler wird parallel ausgeführt und führt daher Aufrufe an SetUploadResult parallel durch. Außer es ist nicht dieselbe In-Memory-Instanz der Entität, aber es ist die gleiche Entität. –
Wie kommt es, dass es nicht die gleiche "In-Memory-Instanz" ist, da jede Instanz ihre eigenen Teile hat? – mm8