0

私は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はこれを保証しますが、各パートを並行してアップロードするとエンティティロジックに問題が発生します。

+0

したがって、Contributionエンティティの同じインスタンスで複数のスレッドが動作しているとしますか? – mm8

+0

修正。コントリビューションエンティティは、各partLengthに対してUploadContributionPartCommandを作成し、各UploadContributionPartCommandHandlerはパラレルで実行され、そのため、SetUploadResultを並列に呼び出します。エンティティのインメモリインスタンスと同じではありませんが、同じエンティティです。 –

+1

それぞれのインスタンスには独自の部分があるので、どのように同じ「メモリ内インスタンス」ではないのですか? – mm8

答えて

0

複数のスレッドが同時にSetUploadResultメソッドを呼び出す可能性があり、競合状態が発生する場合、ロックなどの同期メカニズムを使用してクリティカルセクションを保護する必要があります。https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

あなたはロックフィールドstaticを作る場合、それは例えば、エンティティ・タイプのすべてのインスタンス間で共有されます。:

private static readonly object _lock = new object(); 
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); 

    lock (_lock) //Only one thread at a time can enter this critical section. 
       //The second thread will wait here until the first thread leaves the critical section. 
    { 
     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)); 
     } 
    } 
} 
1

私はそれがSetUploadResultを処理しないように、あなたがContributionをリファクタリングすることができると思います。コントリビューションエンティティと、SetUploadResultの副作用が分離され、Contributionドメインモデルの技術的な問題が解決されます。

SetUploadResultの処理を含むディスパッチャクラスを作成します。

エンティティのロジックの実行が終了すると、実行スレッドはアプリケーションサービスに戻ります。この時点で、エンティティからのイベントをディスパッチャに送ることができます。

長時間実行されているプロセスの場合は、それらをタスクのコレクションとして追加して非同期に実行できます。すべてのタスクが完了したら、ただ待つことができます。 SOでこれを行う方法を検索することができます。

+1

同意します。アップロードの進行状況を追跡することは、明らかにアプリケーションレベルの懸念事項であり、ドメインの問題ではありません。 – guillaume31

+0

フェアコメント。私たちは、寄付や寄付の一部について、ドメインの一部にするという決定を下した議論がたくさんありましたが、そうするべきかどうかを疑うことは合理的です。 –

関連する問題