Consider a package structure for an e-commerce service:
orders/
order.py (Order entity, pure domain logic)
order_repo.py (OrderRepository interface)
order_service.py (OrderService: orchestrates domain logic)
payments/
payment.py (Payment entity)
payment_service.py
notifications/
notification_service.py
infrastructure/
postgres_order_repo.py (implements OrderRepository)
stripe_payment_adapter.py
email_notification_adapter.py
The import graph in this structure:
orders/order_service.py → orders/order.py
orders/order_service.py → orders/order_repo.py (interface only)
payments/payment_service.py → payments/payment.py
notifications/notification_service.py → (nothing in orders or payments)
infrastructure/postgres_order_repo.py → orders/order_repo.py
infrastructure/postgres_order_repo.py → orders/order.py
No cycles. Domain packages do not import infrastructure. Infrastructure imports domain interfaces. The dependency direction is controlled.
Now consider what goes wrong when a developer adds a shortcut:
orders/order_service.py → payments/payment_service.py
payments/payment_service.py → orders/order_service.py
The orders package now depends on the payments package and vice versa. Neither can be tested without the other. Neither can be deployed independently. The import cycle has merged two intended modules into one coupled unit. A cycle detector in CI would have caught this before it merged.