Separation of concerns is one of the most important principles in software design. A common way to adhere to this principle is to divide the application code into several levels of abstraction or layers, each performing a strictly defined function.

Infrastructure Layer
The Infrastructure Layer is the outer layer that handles all interactions between the application and the outside world. It includes all code that works with external dependencies, such as databases, third-party services, API controllers, etc. It contains implementations of entity repositories, adapters for message queues and external systems, and libraries for network operations, file access, and even system timers. ORMs and similar tools are encapsulated here.
Application Layer
The Application Layer contains application services and the interfaces for adapters and repositories used by those services. Application services implement the logic coordinating the interaction of domain entities with the outside world through adapters. These services also manage the lifecycle of aggregate roots: retrieving them from repositories, calling entity methods, initiating state saving, and deleting them from memory. Application services act as intermediaries between the infrastructure and the domain entities, isolating them from each other. The Application Layer has no dependencies on the Infrastructure Layer.
Domain Layer
The Domain Layer contains only the logic related to domain requirements. It houses all entities, aggregate roots, value objects, and other domain elements. This layer has no dependencies on other layers, and objects in this layer do not directly refer to objects in other layers. This ensures the purity and completeness of the domain, although it may require implementing additional logic to bridge gaps.