質問内容
趣味で学習を兼ねてC#でデスクトップアプリを作っていて、
コードが複雑になってきたのでクリーンアーキテクチャで書き直しているのですが、
例えばRepositoryからデータを読み込んで、UseCaseに応じてDomainで処理を行うとして、
保存UseCaseや読み込みUseCaseの実行時に、他の処理(非同期を含む)を行うUseCaseの実行を制限したいのですが
その処理はどのレイヤーに書いたらいいのでしょうか。
Controllerで入力を制限する場合はUseCaseからControllerにそれを伝えることになると思いますが、
調べた限りController->UseCase->Presenterと一方方向になると思いますし、
Controllerでその制限を実装しなかった場合実行時にDomainでエラーになるので、
Applicationレイヤーで実装すべきかなと思うのですが、そのレイヤーで状態を持っていいのか等、
色々ネットでクリーンアーキテクチャについて調べたのですが混乱しています。
通常UIスレッドで同期的に行えば上記問題は発生しませんが、UseCaseにawaitを含むコードがあるので
処理->UI更新までのUseCase単位での排他制御(実行の制限)をする必要があるといった感じです。
こういう問題は重い処理を行うアプリでは頻出すると思うので、同期処理と非同期な重い処理を分けて、
重い処理(タスク)を管理する仕組みがあるのではないかと思うのですが、
そういった仕組みを作って解決するべきでしょうか。
####### 追記
コードの例です。WPFなのでViewModelでBindingを行っていますが、質問の部分はInteractorの各UseCaseです。
例のためTask.Delayを使っていますが、ProcUseCaseを呼び出した直後にLoadUseCaseを呼び出すとエラーになります。
各UseCaseを排他的に呼び出す処理をInteractorに持たせるか、もしくは非同期な処理を管理するクラスを作るか、
このあたりの設計が分からず悩んでいます。
書いていて思ったのですが後者のほうが簡単かもしれません…
C#
1using System; 2using System.Threading.Tasks; 3using System.Windows; 4 5namespace WpfArchTestApp 6{ 7 internal class Entity 8 { 9 public string Data 10 { 11 get { return disposed ? throw new ObjectDisposedException("Entity") : data; } 12 set { data = value; } 13 } 14 private string data; 15 private bool disposed = false; 16 public void Dispose() { disposed = true; } 17 } 18 19 internal class Repository 20 { 21 public async Task<Entity> Load() 22 { 23 await Task.Delay(1000); 24 return new Entity { Data = "TestStringData" }; 25 } 26 27 public async Task Save(Entity _) 28 { 29 await Task.Delay(1000); 30 } 31 } 32 33 internal class DomainService 34 { 35 public static async Task HeavyWork(Entity entity) 36 { 37 for (int i = 0; i < entity.Data.Length; ++i) 38 { 39 for (int j = 0; j < entity.Data.Length; ++j) 40 { 41 if (entity.Data[i] >= entity.Data[j]) continue; 42 char[] charArray = entity.Data.ToCharArray(); 43 charArray[j] = entity.Data[i]; 44 charArray[i] = entity.Data[j]; 45 entity.Data = new string(charArray); 46 await Task.Delay(20); 47 } 48 } 49 } 50 } 51 52 internal class Interactor 53 { 54 public event EventHandler DataUpdated; 55 public string Data { get { return entity == null ? "" : entity.Data; } } 56 57 private readonly Repository repository; 58 private Entity entity; 59 60 public Interactor(Repository repository) 61 { 62 this.repository = repository; 63 } 64 65 public async void LoadUseCase() 66 { 67 if (entity != null) 68 { 69 entity.Dispose(); 70 entity = null; 71 } 72 entity = await repository.Load(); 73 DataUpdated?.Invoke(this, EventArgs.Empty); 74 } 75 76 public async void ProcUseCase() 77 { 78 if (entity == null) return; 79 await DomainService.HeavyWork(entity); // 他のUseCaseが呼ばれるとエラー 80 DataUpdated?.Invoke(this, EventArgs.Empty); 81 } 82 83 public async void SaveUseCase() 84 { 85 if (entity == null) return; 86 await repository.Save(entity); 87 entity.Dispose(); 88 entity = null; 89 DataUpdated?.Invoke(this, EventArgs.Empty); 90 } 91 } 92 93 internal class MainWindowViewModel : VMCommon.ViewModelBase 94 { 95 public VMCommon.DelegateCommand LoadCommand { get; } 96 public VMCommon.DelegateCommand ProcCommand { get; } 97 public VMCommon.DelegateCommand SaveCommand { get; } 98 public string Data 99 { 100 get { return data; } 101 private set 102 { 103 if (data == value) return; 104 data = value; 105 RaiseProeprtyChanged(); 106 } 107 } 108 109 private readonly Interactor interactor; 110 private string data; 111 112 public MainWindowViewModel() 113 { 114 Repository repository = new Repository(); 115 interactor = new Interactor(repository); 116 LoadCommand = new VMCommon.DelegateCommand(_ => interactor.LoadUseCase()); 117 ProcCommand = new VMCommon.DelegateCommand(_ => interactor.ProcUseCase()); 118 SaveCommand = new VMCommon.DelegateCommand(_ => interactor.SaveUseCase()); 119 interactor.DataUpdated += Interactor_DataUpdated; 120 } 121 122 private void Interactor_DataUpdated(object sender, EventArgs e) 123 { 124 Data = interactor.Data; 125 } 126 } 127 128 public partial class MainWindow : Window 129 { 130 public MainWindow() 131 { 132 InitializeComponent(); 133 DataContext = new MainWindowViewModel(); 134 } 135 } 136}