This question concerns the C# language, but I expect it to cover other languages such as Java or TypeScript.
Microsoft recommends best practices on using asynchronous calls in .NET. Among these recommendations, let's pick two:
- change the signature of the async methods so that they return Task or Task<> (in TypeScript, that'd be a Promise<>)
- change the names of the async methods to end with xxxAsync()
Now, when replacing a low-level, synchronous component by an async one, this impacts the full stack of the application. Since async/await has a positive impact only if used "all the way up", it means the signature and method names of every layer in the application must be changed.
A good architecture often involves placing abstractions between each layers, such that replacing low-level components by others is unseen by the upper-level components. In C#, abstractions take the form of interfaces. If we introduce a new, low-level, async component, each interface in the call stack needs to be either modified or replaced by a new interface. The way a problem is solved (async or sync) in an implementing class is not hidden (abstracted) to the callers anymore. The callers have to know if it's sync or async.
Aren't async/await best practices contradicting with "good architecture" principles?
Does it mean that each interface (say IEnumerable, IDataAccessLayer) needs their async counterpart (IAsyncEnumerable, IAsyncDataAccessLayer) such that they can be replaced in the stack when switching to async dependencies?
If we push the problem a little further, wouldn't it be simpler to assume every method to be async (to return a Task<> or Promise<>), and for the methods to synchronize the async calls when they're not actually async? Is this something to be expected from the future programming languages?