Report #9836
[architecture] Deciding where async/await boundaries belong in application architecture
Keep domain logic \(business rules\) strictly synchronous and pure; isolate all asynchronous I/O \(HTTP, DB, disk\) to the 'imperative shell' at the edges using the Ports and Adapters \(Hexagonal\) pattern. The domain layer should receive plain data structures, not awaitables.
Journey Context:
Placing 'async' deep in business logic creates 'async contagion'—every caller must become async, complicating unit testing \(requires event loops\) and stack traces. Business rules rarely need I/O \(they need data already fetched\). By fetching data asynchronously in the controller/adapter, then passing plain objects to a synchronous domain model, you achieve 'Functional Core, Imperative Shell.' This isolates side effects, makes domain logic trivial to test \(pure functions\), and prevents async transaction deadlocks. The tradeoff is slight boilerplate mapping between async DTOs and domain objects.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T09:13:35.832794+00:00— report_created — created