彼の著書「Concurrency in C#Cookbook」で、Stephen Clearyは次のように書いています。
可能であれば、Ports and Adapters(Hexagonal Architecture)などの最新の設計ガイドラインに沿ってコードを整理し、ビジネスロジックをI/Oなどの副作用から分離してください。そのような状況になった場合は、同期APIと非同期APIの両方を公開する必要はありません。ビジネスロジックは常に同期され、I/Oは常に非同期になります。
しかし、私はそれが私の設計で真実であるとはわかりません。同期ドメインと非同期インフラストラクチャコードを持つことができるように、それがどのようになっているのか疑問に思っています。
たとえば、次の場合:
public interface IContentRepository
{
Content GetContent (Guid contentId);
}
public class MyDomainObject
{
public void Foo (IContentRepository repo)
{
var content = GetContent(someId);
...
}
}
public class ContentRepository : IContentRepository
{
// Uh oh, either...
// 1. implement it sync here or
// 2. use sync-over-async or
// 3. make IContentRepository return Task<Content> (and live with an async domain)
}
ドメインが同期のみを維持し、非同期インフラストラクチャコードを利用できるように設計されているはずです。それは可能ですか?スティーブンを誤解しましたか?
Stephen Clearyは、ポートとアダプタのアーキテクチャがすべてのI/Oを外側の層で行い、これらの操作の結果をドメインオブジェクトに渡すことを期待していると思います。あなたの例では、これは単純です:IContentRepository
を注入するのではなく、コンテンツをドメインオブジェクトに渡します。
public class MyDomainObject
{
public void Foo (Content content)
{
...
}
}
ただし、副作用を伴うビジネスルールがある場合、これはさらに困難になります。
例:
public class ContentDomainObject
{
public async void FinalizeContent(IContentRepository repository)
{
if (RequiresRule276aContent)
{
var content = CreateFinalRule276aContent();
var contentId = await repository.AddContent(content);
_contentId = contentId;
ReportDomainEvent(new ContentFinalizedEvent(contentId));
}
else
{
...
}
}
}
これの一部を外側のレイヤーに移動することで、内側のレイヤーにこのasync
の副作用を知らせないようにすることができます。
// Outer layer
var content = contentDomainObject.CreateFinalContent();
var contentId = await repository.AddContent(content);
contentDomainObject.ProcessFinalContent(contentId);
ただし、これが本当に優れているかどうかはわかりません。ドメイン操作が2つの部分に分割されているため、ビジネスの観点からは、その名前が実際には意味をなさない場合があります。したがって、私はおそらく実用的で、async
ドメインオブジェクトメソッドを受け入れることを選択します。
Async-awaitを使い始めると、アプリケーションパイプラインに沿ってゾンビとして広がり、どこでもasyncを使わざるを得なくなることは事実です。
少し異なる角度から設計に取り組みます。ドメインロジックはデータのみを考慮します。
したがって、すべてのIO関連コードをドメインレイヤーの外側に、最上位のレイヤーのできるだけ近くに配置できます。
1必要なデータを読み込む非同期
2データをビジネスロジックに渡す
3プロセスデータ同期的に
4可能な結果を返す
5処理結果を保存非同期
public class MyDomainObject
{
public MyDomainObject(DomainContent content) => _content = content;
public ProcessedContent DoSomething()
{
// Process given content
}
}
// This implementation belongs to the "infrastructure" layer
public class MyDomainProcessor
{
public MyDomainProcessor(IContentRepository repository)
{
_repository = repository;
}
public async Task ProcessAsync(Guid contentId)
{
var content = await _repository.GetContentBy(contentId);
var processedContent = new MyDomainObject(content).DoSomething();
await _repository.Save(processedContent);
}
}
アダプタをドメインに呼び出す必要があります。
非同期コードは、同期コードと非同期コードの両方を呼び出すことができます。ただし、同期コードが非同期を呼び出すと、問題が発生します。
これを修正する最も簡単な方法は、コールバックを渡すことです。
プロジェクトの実行フローはどのようになっていますか?
依存性注入を行うコードは非同期にすることができるため、同期コードと非同期コードの両方を呼び出すことができます。 参照 作曲ルート 。そうですか?境界に非同期コードがあります。
境界内の非同期コードはドメインを簡単に呼び出すことができます。その後、ドメインはインスタンス※を返し、実行する操作(リポジトリのコンテンツを取得するなど)を指示します。
※:いったん渡されると、境界のコードはそれらのインスタンスを変更できないはずです。これを行う最も簡単な方法は、値型を渡すことです。不変オブジェクトも機能するか、単にそれらへの参照を保持しません。 値/参照タイプ、オブジェクトおよびセマンティクス も参照してください。
境界コードがコードから返されたものを取得すると、非同期操作を実行して、ドメインに再度コールバックできます。
実際、これを行う場合、依存性注入を行う必要はまったくありません。